contains 215 rules |
System Settings
[ref]groupContains rules that check correct system settings. |
contains 103 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 13 rules |
System and Software Integrity
[ref]groupSystem and software integrity can be gained by installing antivirus, increasing
system encryption strength with FIPS, verifying installed software, enabling SELinux,
installing an Intrusion Prevention System, etc. However, installing or enabling integrity
checking tools cannot prevent intrusions, but they can detect that an intrusion
may have occurred. Requirements for integrity checking may be highly dependent on
the environment in which the system will be used. Snapshot-based approaches such
as AIDE may induce considerable overhead in the presence of frequent software updates. |
contains 6 rules |
Software Integrity Checking
[ref]groupBoth the AIDE (Advanced Intrusion Detection Environment)
software and the RPM package management system provide
mechanisms for verifying the integrity of installed software.
AIDE uses snapshots of file metadata (such as hashes) and compares these
to current system files in order to detect changes.
The RPM package management system can conduct integrity
checks by comparing information in its metadata database with
files installed on the system. |
contains 5 rules |
Verify Integrity with AIDE
[ref]groupAIDE conducts integrity checks by comparing information about
files with previously-gathered information. Ideally, the AIDE database is
created immediately after initial system configuration, and then again after any
software update. AIDE is highly configurable, with further configuration
information located in /usr/share/doc/aide-VERSION . |
contains 5 rules |
Install AIDE
[ref]ruleThe aide package can be installed with the following command:
$ apt-get install aide Rationale:The AIDE package must be installed if it is to be available for integrity checking. References:
R76, R79, 1.3.1, 1, 11, 12, 13, 14, 15, 16, 2, 3, 5, 7, 8, 9, 5.10.1.3, APO01.06, BAI01.06, BAI02.01, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS04.07, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.02, DSS06.06, CCI-002696, CCI-001744, 4.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4, SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 4.1, SR 6.2, SR 7.6, 1034, 1288, 1341, 1417, A.11.2.4, A.12.1.2, A.12.2.1, A.12.4.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.7, A.15.2.1, A.8.2.3, CM-6(a), DE.CM-1, DE.CM-7, PR.DS-1, PR.DS-6, PR.DS-8, PR.IP-1, PR.IP-3, Req-11.5, 11.5.2, SRG-OS-000445-GPOS-00199, UBTU-22-651010, SV-260582r958944_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-22-651010
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-11.5
- PCI-DSSv4-11.5.2
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_aide_installed
- name: Ensure aide is installed
package:
name: aide
state: present
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-22-651010
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-11.5
- PCI-DSSv4-11.5.2
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_aide_installed
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_aide
class install_aide {
package { 'aide':
ensure => 'installed',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "aide"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[[packages]]
name = "aide"
version = "*"
|
Build and Test AIDE Database
[ref]ruleRun the following command to generate a new database:
$ sudo aideinit
By default, the database will be written to the file
/var/lib/aide/aide.db.new .
Storing the database, the configuration file /etc/aide.conf , and the binary
/usr/bin/aide
(or hashes of these files), in a secure location (such as on read-only media) provides additional assurance about their integrity.
The newly-generated database can be installed as follows:
$ sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db
To initiate a manual check, run the following command:
$ sudo /usr/bin/aide --check
If this check produces any unexpected output, investigate.Rationale:For AIDE to be effective, an initial database of "known-good" information about files
must be captured and it should be able to be verified against the installed files. References:
R76, R79, 1.3.1, 1, 11, 12, 13, 14, 15, 16, 2, 3, 5, 7, 8, 9, 5.10.1.3, APO01.06, BAI01.06, BAI02.01, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS04.07, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.02, DSS06.06, CCI-002696, CCI-001744, 4.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4, SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 4.1, SR 6.2, SR 7.6, A.11.2.4, A.12.1.2, A.12.2.1, A.12.4.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.7, A.15.2.1, A.8.2.3, CM-6(a), DE.CM-1, DE.CM-7, PR.DS-1, PR.DS-6, PR.DS-8, PR.IP-1, PR.IP-3, Req-11.5, 11.5.2, SRG-OS-000445-GPOS-00199, UBTU-22-651015, SV-260583r958944_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-22-651015
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-11.5
- PCI-DSSv4-11.5.2
- aide_build_database
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Build and Test AIDE Database - Ensure AIDE Is Installed
ansible.builtin.apt:
name: aide
state: present
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-22-651015
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-11.5
- PCI-DSSv4-11.5.2
- aide_build_database
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Build and Test AIDE Database - Check if DB Path in /etc/aide/aide.conf Is
Already Set
ansible.builtin.lineinfile:
path: /etc/aide/aide.conf
regexp: ^#?(\s*)(database=)(.*)$
state: absent
check_mode: true
changed_when: false
register: database_replace
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-22-651015
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-11.5
- PCI-DSSv4-11.5.2
- aide_build_database
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Build and Test AIDE Database - Check if DB Out Path in /etc/aide/aide.conf
Is Already Set
ansible.builtin.lineinfile:
path: /etc/aide/aide.conf
regexp: ^#?(\s*)(database_out=)(.*)$
state: absent
check_mode: true
changed_when: false
register: database_out_replace
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-22-651015
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-11.5
- PCI-DSSv4-11.5.2
- aide_build_database
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Build and Test AIDE Database - Fix DB Path in Config File if Necessary
ansible.builtin.lineinfile:
path: /etc/aide/aide.conf
regexp: ^#?(\s*)(database)(\s*)=(\s*)(.*)$
line: \2\3=\4file:/var/lib/aide/aide.db
backrefs: true
when:
- '"linux-base" in ansible_facts.packages'
- database_replace.found > 0
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-22-651015
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-11.5
- PCI-DSSv4-11.5.2
- aide_build_database
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Build and Test AIDE Database - Fix DB Out Path in Config File if Necessary
ansible.builtin.lineinfile:
path: /etc/aide/aide.conf
regexp: ^#?(\s*)(database_out)(\s*)=(\s*)(.*)$
line: \2\3=\4file:/var/lib/aide/aide.db.new
backrefs: true
when:
- '"linux-base" in ansible_facts.packages'
- database_out_replace.found > 0
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-22-651015
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-11.5
- PCI-DSSv4-11.5.2
- aide_build_database
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Build and Test AIDE Database - Ensure the Default DB Path is Added
ansible.builtin.lineinfile:
path: /etc/aide/aide.conf
line: database=file:/var/lib/aide/aide.db
create: true
when:
- '"linux-base" in ansible_facts.packages'
- database_replace.found == 0
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-22-651015
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-11.5
- PCI-DSSv4-11.5.2
- aide_build_database
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Build and Test AIDE Database - Ensure the Default Out Path is Added
ansible.builtin.lineinfile:
path: /etc/aide/aide.conf
line: database_out=file:/var/lib/aide/aide.db.new
create: true
when:
- '"linux-base" in ansible_facts.packages'
- database_out_replace.found == 0
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-22-651015
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-11.5
- PCI-DSSv4-11.5.2
- aide_build_database
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Build and Test AIDE Database - Build and Test AIDE Database
ansible.builtin.command: /usr/sbin/aideinit -y -f
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-22-651015
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-11.5
- PCI-DSSv4-11.5.2
- aide_build_database
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "aide"
AIDE_CONFIG=/etc/aide/aide.conf
DEFAULT_DB_PATH=/var/lib/aide/aide.db
# Fix db path in the config file, if necessary
if ! grep -q '^database=file:' ${AIDE_CONFIG}; then
# replace_or_append gets confused by 'database=file' as a key, so should not be used.
#replace_or_append "${AIDE_CONFIG}" '^database=file' "${DEFAULT_DB_PATH}" '@CCENUM@' '%s:%s'
echo "database=file:${DEFAULT_DB_PATH}" >> ${AIDE_CONFIG}
fi
# Fix db out path in the config file, if necessary
if ! grep -q '^database_out=file:' ${AIDE_CONFIG}; then
echo "database_out=file:${DEFAULT_DB_PATH}.new" >> ${AIDE_CONFIG}
fi
/usr/sbin/aideinit -y -f
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure AIDE to Verify the Audit Tools
[ref]ruleThe operating system file integrity tool must be configured to protect the integrity of the audit tools. Rationale:Protecting the integrity of the tools used for auditing purposes is a
critical step toward ensuring the integrity of audit information. Audit
information includes all information (e.g., audit records, audit settings,
and audit reports) needed to successfully audit information system
activity.
Audit tools include but are not limited to vendor-provided and open-source
audit tools needed to successfully view and manipulate audit information
system activity and records. Audit tools include custom queries and report
generators.
It is not uncommon for attackers to replace the audit tools or inject code
into the existing tools to provide the capability to hide or erase system
activity from the audit logs.
To address this risk, audit tools must be cryptographically signed to
provide the capability to identify when the audit tools have been modified,
manipulated, or replaced. An example is a checksum hash of the file or
files. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-651030
- NIST-800-53-AU-9(3)
- NIST-800-53-AU-9(3).1
- aide_check_audit_tools
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Configure AIDE to Verify the Audit Tools - Gather List of Packages
tags:
- DISA-STIG-UBTU-22-651030
- NIST-800-53-AU-9(3)
- NIST-800-53-AU-9(3).1
- aide_check_audit_tools
- aide_check_audit_tools
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
ansible.builtin.package_facts:
manager: auto
when: '"linux-base" in ansible_facts.packages'
- name: Ensure aide is installed
package:
name: '{{ item }}'
state: present
with_items:
- aide
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-651030
- NIST-800-53-AU-9(3)
- NIST-800-53-AU-9(3).1
- aide_check_audit_tools
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Set audit_tools fact
set_fact:
audit_tools:
- /usr/sbin/auditctl
- /usr/sbin/auditd
- /usr/sbin/augenrules
- /usr/sbin/aureport
- /usr/sbin/ausearch
- /usr/sbin/autrace
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-651030
- NIST-800-53-AU-9(3)
- NIST-800-53-AU-9(3).1
- aide_check_audit_tools
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure existing AIDE configuration for audit tools are correct
lineinfile:
path: /etc/aide/aide.conf
regexp: ^{{ item }}\s
line: '{{ item }} p+i+n+u+g+s+b+acl+xattrs+sha512'
with_items: '{{ audit_tools }}'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-651030
- NIST-800-53-AU-9(3)
- NIST-800-53-AU-9(3).1
- aide_check_audit_tools
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Configure AIDE to properly protect audit tools
lineinfile:
path: /etc/aide/aide.conf
line: '{{ item }} p+i+n+u+g+s+b+acl+xattrs+sha512'
with_items: '{{ audit_tools }}'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-651030
- NIST-800-53-AU-9(3)
- NIST-800-53-AU-9(3).1
- aide_check_audit_tools
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "aide"
if grep -i '^.*/usr/sbin/auditctl.*$' /etc/aide/aide.conf; then
sed -i "s#.*/usr/sbin/auditctl.*#/usr/sbin/auditctl p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide/aide.conf
else
echo "/usr/sbin/auditctl p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide/aide.conf
fi
if grep -i '^.*/usr/sbin/auditd.*$' /etc/aide/aide.conf; then
sed -i "s#.*/usr/sbin/auditd.*#/usr/sbin/auditd p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide/aide.conf
else
echo "/usr/sbin/auditd p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide/aide.conf
fi
if grep -i '^.*/usr/sbin/ausearch.*$' /etc/aide/aide.conf; then
sed -i "s#.*/usr/sbin/ausearch.*#/usr/sbin/ausearch p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide/aide.conf
else
echo "/usr/sbin/ausearch p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide/aide.conf
fi
if grep -i '^.*/usr/sbin/aureport.*$' /etc/aide/aide.conf; then
sed -i "s#.*/usr/sbin/aureport.*#/usr/sbin/aureport p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide/aide.conf
else
echo "/usr/sbin/aureport p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide/aide.conf
fi
if grep -i '^.*/usr/sbin/autrace.*$' /etc/aide/aide.conf; then
sed -i "s#.*/usr/sbin/autrace.*#/usr/sbin/autrace p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide/aide.conf
else
echo "/usr/sbin/autrace p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide/aide.conf
fi
if grep -i '^.*/usr/sbin/augenrules.*$' /etc/aide/aide.conf; then
sed -i "s#.*/usr/sbin/augenrules.*#/usr/sbin/augenrules p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide/aide.conf
else
echo "/usr/sbin/augenrules p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide/aide.conf
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure AIDE To Notify Personnel if Baseline Configurations Are Altered
[ref]ruleThe operating system file integrity tool must be configured to notify designated personnel of any changes to configurations. Rationale:Detecting changes in the system can help avoid unintended, and negative consequences
that could affect the security state of the operating system Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-651020
- aide_disable_silentreports
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Configure AIDE To Notify Personnel if Baseline Configurations Are Altered
block:
- name: Check for duplicate values
lineinfile:
path: /etc/default/aide
create: true
regexp: (?i)^\s*SILENTREPORTS=
state: absent
check_mode: true
changed_when: false
register: dupes
- name: Deduplicate values from /etc/default/aide
lineinfile:
path: /etc/default/aide
create: true
regexp: (?i)^\s*SILENTREPORTS=
state: absent
when: dupes.found is defined and dupes.found > 1
- name: Insert correct line to /etc/default/aide
lineinfile:
path: /etc/default/aide
create: true
regexp: (?i)^\s*SILENTREPORTS=
line: SILENTREPORTS=no
state: present
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-651020
- aide_disable_silentreports
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
if [ -e "/etc/default/aide" ] ; then
LC_ALL=C sed -i "/^\s*SILENTREPORTS=/Id" "/etc/default/aide"
else
touch "/etc/default/aide"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/default/aide"
cp "/etc/default/aide" "/etc/default/aide.bak"
# Insert at the end of the file
printf '%s\n' "SILENTREPORTS=no" >> "/etc/default/aide"
# Clean up after ourselves.
rm "/etc/default/aide.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure Periodic Execution of AIDE
[ref]ruleAt a minimum, AIDE should be configured to run a weekly scan.
To implement a daily execution of AIDE at 4:05am using cron, add the following line to /etc/crontab :
05 4 * * * root /usr/bin/aide --config /etc/aide/aide.conf --check
To implement a weekly execution of AIDE at 4:05am using cron, add the following line to /etc/crontab :
05 4 * * 0 root /usr/bin/aide --config /etc/aide/aide.conf --check
AIDE can be executed periodically through other means; this is merely one example.
The usage of cron's special time codes, such as @daily and
@weekly is acceptable.Rationale:By default, AIDE does not install itself for periodic execution. Periodically
running AIDE is necessary to reveal unexpected changes in installed files.
Unauthorized changes to the baseline configuration could make the system vulnerable
to various attacks or allow unauthorized access to the operating system. Changes to
operating system configurations can have unintended side effects, some of which may
be relevant to security.
Detecting such changes and providing an automated response can help avoid unintended,
negative consequences that could ultimately affect the security state of the operating
system. The operating system's Information Management Officer (IMO)/Information System
Security Officer (ISSO) and System Administrators (SAs) must be notified via email and/or
monitoring system trap when there is an unauthorized modification of a configuration item. References:
R76, 1.3.2, 1, 11, 12, 13, 14, 15, 16, 2, 3, 5, 7, 8, 9, 5.10.1.3, APO01.06, BAI01.06, BAI02.01, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS04.07, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.02, DSS06.06, CCI-002702, CCI-001744, CCI-002699, 4.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4, SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 4.1, SR 6.2, SR 7.6, A.11.2.4, A.12.1.2, A.12.2.1, A.12.4.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.7, A.15.2.1, A.8.2.3, SI-7, SI-7(1), CM-6(a), DE.CM-1, DE.CM-7, PR.DS-1, PR.DS-6, PR.DS-8, PR.IP-1, PR.IP-3, Req-11.5, 11.5.2, SRG-OS-000363-GPOS-00150, SRG-OS-000446-GPOS-00200, SRG-OS-000447-GPOS-00201, UBTU-22-651025, SV-260585r958946_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-22-651025
- NIST-800-53-CM-6(a)
- NIST-800-53-SI-7
- NIST-800-53-SI-7(1)
- PCI-DSS-Req-11.5
- PCI-DSSv4-11.5.2
- aide_periodic_cron_checking
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure AIDE is installed
package:
name: '{{ item }}'
state: present
with_items:
- aide
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-22-651025
- NIST-800-53-CM-6(a)
- NIST-800-53-SI-7
- NIST-800-53-SI-7(1)
- PCI-DSS-Req-11.5
- PCI-DSSv4-11.5.2
- aide_periodic_cron_checking
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Set cron package name - RedHat
set_fact:
cron_pkg_name: cronie
when:
- '"linux-base" in ansible_facts.packages'
- ansible_os_family == "RedHat" or ansible_os_family == "Suse"
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-22-651025
- NIST-800-53-CM-6(a)
- NIST-800-53-SI-7
- NIST-800-53-SI-7(1)
- PCI-DSS-Req-11.5
- PCI-DSSv4-11.5.2
- aide_periodic_cron_checking
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Set cron package name - Debian
set_fact:
cron_pkg_name: cron
when:
- '"linux-base" in ansible_facts.packages'
- ansible_os_family == "Debian"
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-22-651025
- NIST-800-53-CM-6(a)
- NIST-800-53-SI-7
- NIST-800-53-SI-7(1)
- PCI-DSS-Req-11.5
- PCI-DSSv4-11.5.2
- aide_periodic_cron_checking
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Install cron
package:
name: '{{ cron_pkg_name }}'
state: present
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-22-651025
- NIST-800-53-CM-6(a)
- NIST-800-53-SI-7
- NIST-800-53-SI-7(1)
- PCI-DSS-Req-11.5
- PCI-DSSv4-11.5.2
- aide_periodic_cron_checking
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Configure Periodic Execution of AIDE
cron:
name: run AIDE check
minute: 5
hour: 4
weekday: 0
user: root
job: /usr/bin/aide --check
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.10.1.3
- DISA-STIG-UBTU-22-651025
- NIST-800-53-CM-6(a)
- NIST-800-53-SI-7
- NIST-800-53-SI-7(1)
- PCI-DSS-Req-11.5
- PCI-DSSv4-11.5.2
- aide_periodic_cron_checking
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "aide"
# AiDE usually adds its own cron jobs to /etc/cron.daily. If script is there, this rule is
# compliant. Otherwise, we copy the script to the /etc/cron.weekly
if ! grep -Eq '^(\/usr\/bin\/)?aide(\.wrapper)?\s+' /etc/cron.*/*; then
cp -f /usr/share/aide/config/cron.daily/aide /etc/cron.weekly/
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Federal Information Processing Standard (FIPS)
[ref]groupThe Federal Information Processing Standard (FIPS) is a computer security standard which
is developed by the U.S. Government and industry working groups to validate the quality
of cryptographic modules. The FIPS standard provides four security levels to ensure
adequate coverage of different industries, implementation of cryptographic modules, and
organizational sizes and requirements.
FIPS 140-2 is the current standard for validating that mechanisms used to access cryptographic modules
utilize authentication that meets industry and government requirements. For government systems, this allows
Security Levels 1, 2, 3, or 4 for use on Ubuntu 22.04.
See http://csrc.nist.gov/publications/PubsFIPS.html for more information. |
contains 1 rule |
Verify '/proc/sys/crypto/fips_enabled' exists
[ref]ruleOn a system where FIPS 140-2 mode is enabled, /proc/sys/crypto/fips_enabled must exist.
To verify FIPS mode, run the following command:
cat /proc/sys/crypto/fips_enabled Warning:
To configure the OS to run in FIPS 140-2 mode, the kernel parameter "fips=1" needs to be added during its installation.
Enabling FIPS mode on a preexisting system involves a number of modifications to it. Refer to the vendor installation
guidances. Warning:
System Crypto Modules must be provided by a vendor that undergoes
FIPS-140 certifications.
FIPS-140 is applicable to all Federal agencies that use
cryptographic-based security systems to protect sensitive information
in computer and telecommunication systems (including voice systems) as
defined in Section 5131 of the Information Technology Management Reform
Act of 1996, Public Law 104-106. This standard shall be used in
designing and implementing cryptographic modules that Federal
departments and agencies operate or are operated for them under
contract. See https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.140-2.pdf
To meet this, the system has to have cryptographic software provided by
a vendor that has undergone this certification. This means providing
documentation, test results, design information, and independent third
party review by an accredited lab. While open source software is
capable of meeting this, it does not meet FIPS-140 unless the vendor
submits to this process. Rationale:Use of weak or untested encryption algorithms undermines the purposes of utilizing encryption to
protect data. The operating system must implement cryptographic modules adhering to the higher
standards approved by the federal government since this provides assurance they have been tested
and validated. |
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. |
contains 1 rule |
Encrypt Partitions
[ref]ruleUbuntu 22.04 natively supports partition encryption through the
Linux Unified Key Setup-on-disk-format (LUKS) technology. The easiest way to
encrypt a partition is during installation time.
For manual installations, select the Encrypt checkbox during
partition creation to encrypt the partition. When this
option is selected the system will prompt for a passphrase to use in
decrypting the partition. The passphrase will subsequently need to be entered manually
every time the system boots.
Detailed information on encrypting partitions using LUKS or LUKS ciphers can be found on
the Ubuntu 22.04 Documentation web site:
https://help.ubuntu.com/community/Full_Disk_Encryption_Howto_2019
. Rationale:The risk of a system's physical compromise, particularly mobile systems such as
laptops, places its data at risk of compromise. Encrypting this data mitigates
the risk of its loss if the system is lost. References:
13, 14, APO01.06, BAI02.01, BAI06.01, DSS04.07, DSS05.03, DSS05.04, DSS05.07, DSS06.02, DSS06.06, 3.13.16, CCI-002476, CCI-001199, CCI-002475, 164.308(a)(1)(ii)(D), 164.308(b)(1), 164.310(d), 164.312(a)(1), 164.312(a)(2)(iii), 164.312(a)(2)(iv), 164.312(b), 164.312(c), 164.314(b)(2)(i), 164.312(d), SR 3.4, SR 4.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R4.2, CIP-007-3 R5.1, CM-6(a), SC-28, SC-28(1), SC-13, AU-9(3), PR.DS-1, PR.DS-5, SRG-OS-000405-GPOS-00184, SRG-OS-000185-GPOS-00079, SRG-OS-000404-GPOS-00183, UBTU-22-231010, SV-260484r958552_rule |
GNOME Desktop Environment
[ref]groupGNOME is a graphical desktop environment bundled with many Linux distributions that
allow users to easily interact with the operating system graphically rather than
textually. The GNOME Graphical Display Manager (GDM) provides login, logout, and user
switching contexts as well as display server management.
GNOME is developed by the GNOME Project and is considered the default
Red Hat Graphical environment.
For more information on GNOME and the GNOME Project, see https://www.gnome.org. |
contains 4 rules |
Configure GNOME Screen Locking
[ref]groupIn the default GNOME3 desktop, the screen can be locked
by selecting the user name in the far right corner of the main panel and
selecting Lock.
The following sections detail commands to enforce idle activation of the screensaver,
screen locking, a blank-screen screensaver, and an idle activation time.
Because users should be trained to lock the screen when they
step away from the computer, the automatic locking feature is only
meant as a backup.
The root account can be screen-locked; however, the root account should
never be used to log into an X Windows environment and should only
be used to for direct login via console in emergency circumstances.
For more information about enforcing preferences in the GNOME3 environment using the DConf
configuration system, see http://wiki.gnome.org/dconf and
the man page dconf(1) . |
contains 3 rules |
Set GNOME3 Screensaver Inactivity Timeout
[ref]ruleThe idle time-out value for inactivity in the GNOME3 desktop is configured via the idle-delay
setting must be set under an appropriate configuration file(s) in the /etc/dconf/db/local.d directory
and locked in /etc/dconf/db/local.d/locks directory to prevent user modification.
For example, to configure the system for a 15 minute delay, add the following to
/etc/dconf/db/local.d/00-security-settings :
[org/gnome/desktop/session]
idle-delay=uint32 900 Rationale:A session time-out lock is a temporary action taken when a user stops work and moves away from
the immediate physical vicinity of the information system but does not logout because of the
temporary nature of the absence. Rather than relying on the user to manually lock their operating
system session prior to vacating the vicinity, GNOME3 can be configured to identify when
a user's session has idled and take action to initiate a session lock. References:
1, 12, 15, 16, 5.5.5, DSS05.04, DSS05.10, DSS06.10, 3.1.10, CCI-000057, CCI-000060, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9, A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3, AC-11(a), CM-6(a), PR.AC-7, Req-8.1.8, 8.2.8, 8.2, SRG-OS-000029-GPOS-00010, SRG-OS-000031-GPOS-00012, UBTU-22-271025, SV-260538r958402_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'gdm3' 2>/dev/null | grep -q '^installed' && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then
mkdir -p /etc/dconf/profile
dconf_profile_path=/etc/dconf/profile/user
[[ -s "${dconf_profile_path}" ]] || echo > "${dconf_profile_path}"
if ! grep -Pzq "(?s)^\s*user-db:user.*\n\s*system-db:local" "${dconf_profile_path}"; then
sed -i --follow-symlinks "1s/^/user-db:user\nsystem-db:local\n/" "${dconf_profile_path}"
fi
# Make sure the corresponding directories exist
mkdir -p /etc/dconf/db/local.d
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/profile
(umask 0022 && dconf update)
mkdir -p /etc/dconf/profile
dconf_profile_path=/etc/dconf/profile/gdm
[[ -s "${dconf_profile_path}" ]] || echo > "${dconf_profile_path}"
if ! grep -Pzq "(?s)^\s*user-db:user.*\n\s*system-db:gdm" "${dconf_profile_path}"; then
sed -i --follow-symlinks "1s/^/user-db:user\nsystem-db:gdm\n/" "${dconf_profile_path}"
fi
# Make sure the corresponding directories exist
mkdir -p /etc/dconf/db/gdm.d
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/profile
(umask 0022 && dconf update)
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/org/gnome/desktop/screensaver/idle-delay$" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|local.d' | grep ":" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/local.d/locks"
mkdir -p "${LOCKSFOLDER}"
# Comment out the configurations in databases different from the target one
if [[ ! -z "${LOCKFILES}" ]]
then
sed -i -E "s|^/org/gnome/desktop/screensaver/idle-delay$|#&|" "${LOCKFILES[@]}"
fi
if ! grep -qr "^/org/gnome/desktop/screensaver/idle-delay$" /etc/dconf/db/local.d/
then
echo "/org/gnome/desktop/screensaver/idle-delay" >> "/etc/dconf/db/local.d/locks/00-security-settings-lock"
fi
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/db
(umask 0022 && dconf update)
inactivity_timeout_value='900'
# Check for setting in any of the DConf db directories
# If files contain ibus or distro, ignore them.
# The assignment assumes that individual filenames don't contain :
readarray -t SETTINGSFILES < <(grep -r "\\[org/gnome/desktop/session\\]" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|local.d' | cut -d":" -f1)
DCONFFILE="/etc/dconf/db/local.d/00-security-settings"
DBDIR="/etc/dconf/db/local.d"
mkdir -p "${DBDIR}"
# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
if grep -q "^\\s*idle-delay\\s*=" "${SETTINGSFILES[@]}"
then
sed -Ei "s/(^\s*)idle-delay(\s*=)/#\1idle-delay\2/g" "${SETTINGSFILES[@]}"
fi
fi
[ ! -z "${DCONFFILE}" ] && echo "" >> "${DCONFFILE}"
if ! grep -q "\\[org/gnome/desktop/session\\]" "${DCONFFILE}"
then
printf '%s\n' "[org/gnome/desktop/session]" >> ${DCONFFILE}
fi
escaped_value="$(sed -e 's/\\/\\\\/g' <<< "uint32 ${inactivity_timeout_value}")"
if grep -q "^\\s*idle-delay\\s*=" "${DCONFFILE}"
then
sed -i "s/\\s*idle-delay\\s*=\\s*.*/idle-delay=${escaped_value}/g" "${DCONFFILE}"
else
sed -i "\\|\\[org/gnome/desktop/session\\]|a\\idle-delay=${escaped_value}" "${DCONFFILE}"
fi
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/db
(umask 0022 && dconf update)
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Set GNOME3 Screensaver Lock Delay After Activation Period
[ref]ruleTo activate the locking delay of the screensaver in the GNOME3 desktop when
the screensaver is activated, add or set lock-delay to uint32 0 in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/desktop/screensaver]
lock-delay=uint32 0
After the settings have been set, run dconf update .Rationale:A session lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity
of the information system but does not want to logout because of the temporary nature of the absense. References:
1.8.5, 1, 12, 15, 16, DSS05.04, DSS05.10, DSS06.10, 3.1.10, CCI-000057, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9, A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3, AC-11(a), CM-6(a), PR.AC-7, Req-8.1.8, 8.2.8, 8.2, SRG-OS-000029-GPOS-00010, SRG-OS-000031-GPOS-00012, UBTU-22-271025, SV-260538r958402_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'gdm3' 2>/dev/null | grep -q '^installed' && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then
mkdir -p /etc/dconf/profile
dconf_profile_path=/etc/dconf/profile/user
[[ -s "${dconf_profile_path}" ]] || echo > "${dconf_profile_path}"
if ! grep -Pzq "(?s)^\s*user-db:user.*\n\s*system-db:local" "${dconf_profile_path}"; then
sed -i --follow-symlinks "1s/^/user-db:user\nsystem-db:local\n/" "${dconf_profile_path}"
fi
# Make sure the corresponding directories exist
mkdir -p /etc/dconf/db/local.d
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/profile
(umask 0022 && dconf update)
mkdir -p /etc/dconf/profile
dconf_profile_path=/etc/dconf/profile/gdm
[[ -s "${dconf_profile_path}" ]] || echo > "${dconf_profile_path}"
if ! grep -Pzq "(?s)^\s*user-db:user.*\n\s*system-db:gdm" "${dconf_profile_path}"; then
sed -i --follow-symlinks "1s/^/user-db:user\nsystem-db:gdm\n/" "${dconf_profile_path}"
fi
# Make sure the corresponding directories exist
mkdir -p /etc/dconf/db/gdm.d
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/profile
(umask 0022 && dconf update)
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/org/gnome/desktop/screensaver/lock-delay$" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|local.d' | grep ":" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/local.d/locks"
mkdir -p "${LOCKSFOLDER}"
# Comment out the configurations in databases different from the target one
if [[ ! -z "${LOCKFILES}" ]]
then
sed -i -E "s|^/org/gnome/desktop/screensaver/lock-delay$|#&|" "${LOCKFILES[@]}"
fi
if ! grep -qr "^/org/gnome/desktop/screensaver/lock-delay$" /etc/dconf/db/local.d/
then
echo "/org/gnome/desktop/screensaver/lock-delay" >> "/etc/dconf/db/local.d/locks/00-security-settings-lock"
fi
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/db
(umask 0022 && dconf update)
var_screensaver_lock_delay='0'
# Check for setting in any of the DConf db directories
# If files contain ibus or distro, ignore them.
# The assignment assumes that individual filenames don't contain :
readarray -t SETTINGSFILES < <(grep -r "\\[org/gnome/desktop/screensaver\\]" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|local.d' | cut -d":" -f1)
DCONFFILE="/etc/dconf/db/local.d/00-security-settings"
DBDIR="/etc/dconf/db/local.d"
mkdir -p "${DBDIR}"
# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
if grep -q "^\\s*lock-delay\\s*=" "${SETTINGSFILES[@]}"
then
sed -Ei "s/(^\s*)lock-delay(\s*=)/#\1lock-delay\2/g" "${SETTINGSFILES[@]}"
fi
fi
[ ! -z "${DCONFFILE}" ] && echo "" >> "${DCONFFILE}"
if ! grep -q "\\[org/gnome/desktop/screensaver\\]" "${DCONFFILE}"
then
printf '%s\n' "[org/gnome/desktop/screensaver]" >> ${DCONFFILE}
fi
escaped_value="$(sed -e 's/\\/\\\\/g' <<< "uint32 ${var_screensaver_lock_delay}")"
if grep -q "^\\s*lock-delay\\s*=" "${DCONFFILE}"
then
sed -i "s/\\s*lock-delay\\s*=\\s*.*/lock-delay=${escaped_value}/g" "${DCONFFILE}"
else
sed -i "\\|\\[org/gnome/desktop/screensaver\\]|a\\lock-delay=${escaped_value}" "${DCONFFILE}"
fi
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/db
(umask 0022 && dconf update)
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Enable GNOME3 Screensaver Lock After Idle Period
[ref]rule
To activate locking of the screensaver in the GNOME3 desktop when it is activated,
add or set lock-enabled to true in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/desktop/screensaver]
lock-enabled=true
Once the settings have been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/desktop/screensaver/lock-enabled
After the settings have been set, run dconf update .Rationale:A session lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity
of the information system but does not want to logout because of the temporary nature of the absense. References:
1.8.4, 1, 12, 15, 16, 5.5.5, DSS05.04, DSS05.10, DSS06.10, 3.1.10, CCI-000057, CCI-000056, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9, A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3, CM-6(a), PR.AC-7, Req-8.1.8, 8.2.8, 8.2, SRG-OS-000028-GPOS-00009, SRG-OS-000030-GPOS-00011, UBTU-22-271020, SV-260537r958400_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'gdm3' 2>/dev/null | grep -q '^installed' && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then
mkdir -p /etc/dconf/profile
dconf_profile_path=/etc/dconf/profile/user
[[ -s "${dconf_profile_path}" ]] || echo > "${dconf_profile_path}"
if ! grep -Pzq "(?s)^\s*user-db:user.*\n\s*system-db:local" "${dconf_profile_path}"; then
sed -i --follow-symlinks "1s/^/user-db:user\nsystem-db:local\n/" "${dconf_profile_path}"
fi
# Make sure the corresponding directories exist
mkdir -p /etc/dconf/db/local.d
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/profile
(umask 0022 && dconf update)
mkdir -p /etc/dconf/profile
dconf_profile_path=/etc/dconf/profile/gdm
[[ -s "${dconf_profile_path}" ]] || echo > "${dconf_profile_path}"
if ! grep -Pzq "(?s)^\s*user-db:user.*\n\s*system-db:gdm" "${dconf_profile_path}"; then
sed -i --follow-symlinks "1s/^/user-db:user\nsystem-db:gdm\n/" "${dconf_profile_path}"
fi
# Make sure the corresponding directories exist
mkdir -p /etc/dconf/db/gdm.d
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/profile
(umask 0022 && dconf update)
# Check for setting in any of the DConf db directories
# If files contain ibus or distro, ignore them.
# The assignment assumes that individual filenames don't contain :
readarray -t SETTINGSFILES < <(grep -r "\\[org/gnome/desktop/screensaver\\]" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|local.d' | cut -d":" -f1)
DCONFFILE="/etc/dconf/db/local.d/00-security-settings"
DBDIR="/etc/dconf/db/local.d"
mkdir -p "${DBDIR}"
# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
if grep -q "^\\s*lock-enabled\\s*=" "${SETTINGSFILES[@]}"
then
sed -Ei "s/(^\s*)lock-enabled(\s*=)/#\1lock-enabled\2/g" "${SETTINGSFILES[@]}"
fi
fi
[ ! -z "${DCONFFILE}" ] && echo "" >> "${DCONFFILE}"
if ! grep -q "\\[org/gnome/desktop/screensaver\\]" "${DCONFFILE}"
then
printf '%s\n' "[org/gnome/desktop/screensaver]" >> ${DCONFFILE}
fi
escaped_value="$(sed -e 's/\\/\\\\/g' <<< "true")"
if grep -q "^\\s*lock-enabled\\s*=" "${DCONFFILE}"
then
sed -i "s/\\s*lock-enabled\\s*=\\s*.*/lock-enabled=${escaped_value}/g" "${DCONFFILE}"
else
sed -i "\\|\\[org/gnome/desktop/screensaver\\]|a\\lock-enabled=${escaped_value}" "${DCONFFILE}"
fi
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/db
(umask 0022 && dconf update)
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/org/gnome/desktop/screensaver/lock-enabled$" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|local.d' | grep ":" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/local.d/locks"
mkdir -p "${LOCKSFOLDER}"
# Comment out the configurations in databases different from the target one
if [[ ! -z "${LOCKFILES}" ]]
then
sed -i -E "s|^/org/gnome/desktop/screensaver/lock-enabled$|#&|" "${LOCKFILES[@]}"
fi
if ! grep -qr "^/org/gnome/desktop/screensaver/lock-enabled$" /etc/dconf/db/local.d/
then
echo "/org/gnome/desktop/screensaver/lock-enabled" >> "/etc/dconf/db/local.d/locks/00-security-settings-lock"
fi
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/db
(umask 0022 && dconf update)
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
GNOME System Settings
[ref]groupGNOME provides configuration and functionality to a graphical desktop environment
that changes grahical configurations or allow a user to perform
actions that users normally would not be able to do in non-graphical mode such as
remote access configuration, power policies, Geo-location, etc.
Configuring such settings in GNOME will prevent accidential graphical configuration
changes by users from taking place. |
contains 1 rule |
Disable Ctrl-Alt-Del Reboot Key Sequence in GNOME3
[ref]ruleBy default, GNOME will reboot the system if the
Ctrl-Alt-Del key sequence is pressed.
To configure the system to ignore the Ctrl-Alt-Del key sequence
from the Graphical User Interface (GUI) instead of rebooting the system,
add or set logout to [''] in
/etc/dconf/db/local.d/00-security-settings . For example:
[org/gnome/settings-daemon/plugins/media-keys]
logout=['']
Once the settings have been added, add a lock to
/etc/dconf/db/local.d/locks/00-security-settings-lock to prevent
user modification. For example:
/org/gnome/settings-daemon/plugins/media-keys/logout
After the settings have been set, run dconf update .Rationale:A locally logged-in user who presses Ctrl-Alt-Del, when at the console,
can reboot the system. If accidentally pressed, as could happen in
the case of mixed OS environment, this can create the risk of short-term
loss of availability of systems due to unintentional reboot. References:
12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, 3.1.2, CCI-000366, 4.3.3.7.3, SR 2.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CM-6(a), AC-6(1), CM-7(b), PR.AC-4, PR.DS-5, SRG-OS-000480-GPOS-00227, UBTU-22-271030, SV-260539r991589_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'gdm3' 2>/dev/null | grep -q '^installed' && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then
mkdir -p /etc/dconf/profile
dconf_profile_path=/etc/dconf/profile/user
[[ -s "${dconf_profile_path}" ]] || echo > "${dconf_profile_path}"
if ! grep -Pzq "(?s)^\s*user-db:user.*\n\s*system-db:local" "${dconf_profile_path}"; then
sed -i --follow-symlinks "1s/^/user-db:user\nsystem-db:local\n/" "${dconf_profile_path}"
fi
# Make sure the corresponding directories exist
mkdir -p /etc/dconf/db/local.d
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/profile
(umask 0022 && dconf update)
mkdir -p /etc/dconf/profile
dconf_profile_path=/etc/dconf/profile/gdm
[[ -s "${dconf_profile_path}" ]] || echo > "${dconf_profile_path}"
if ! grep -Pzq "(?s)^\s*user-db:user.*\n\s*system-db:gdm" "${dconf_profile_path}"; then
sed -i --follow-symlinks "1s/^/user-db:user\nsystem-db:gdm\n/" "${dconf_profile_path}"
fi
# Make sure the corresponding directories exist
mkdir -p /etc/dconf/db/gdm.d
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/profile
(umask 0022 && dconf update)
# Check for setting in any of the DConf db directories
# If files contain ibus or distro, ignore them.
# The assignment assumes that individual filenames don't contain :
readarray -t SETTINGSFILES < <(grep -r "\\[org/gnome/settings-daemon/plugins/media-keys\\]" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|local.d' | cut -d":" -f1)
DCONFFILE="/etc/dconf/db/local.d/00-security-settings"
DBDIR="/etc/dconf/db/local.d"
mkdir -p "${DBDIR}"
# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
if grep -q "^\\s*logout\\s*=" "${SETTINGSFILES[@]}"
then
sed -Ei "s/(^\s*)logout(\s*=)/#\1logout\2/g" "${SETTINGSFILES[@]}"
fi
fi
[ ! -z "${DCONFFILE}" ] && echo "" >> "${DCONFFILE}"
if ! grep -q "\\[org/gnome/settings-daemon/plugins/media-keys\\]" "${DCONFFILE}"
then
printf '%s\n' "[org/gnome/settings-daemon/plugins/media-keys]" >> ${DCONFFILE}
fi
escaped_value="$(sed -e 's/\\/\\\\/g' <<< "['']")"
if grep -q "^\\s*logout\\s*=" "${DCONFFILE}"
then
sed -i "s/\\s*logout\\s*=\\s*.*/logout=${escaped_value}/g" "${DCONFFILE}"
else
sed -i "\\|\\[org/gnome/settings-daemon/plugins/media-keys\\]|a\\logout=${escaped_value}" "${DCONFFILE}"
fi
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/db
(umask 0022 && dconf update)
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/org/gnome/settings-daemon/plugins/media-keys/logout$" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|local.d' | grep ":" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/local.d/locks"
mkdir -p "${LOCKSFOLDER}"
# Comment out the configurations in databases different from the target one
if [[ ! -z "${LOCKFILES}" ]]
then
sed -i -E "s|^/org/gnome/settings-daemon/plugins/media-keys/logout$|#&|" "${LOCKFILES[@]}"
fi
if ! grep -qr "^/org/gnome/settings-daemon/plugins/media-keys/logout$" /etc/dconf/db/local.d/
then
echo "/org/gnome/settings-daemon/plugins/media-keys/logout" >> "/etc/dconf/db/local.d/locks/00-security-settings-lock"
fi
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/db
(umask 0022 && dconf update)
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Sudo , which stands for "su 'do'", provides the ability to delegate authority
to certain users, groups of users, or system administrators. When configured for system
users and/or groups, Sudo can allow a user or group to execute privileged commands
that normally only root is allowed to execute.
For more information on Sudo and addition Sudo configuration options, see
https://www.sudo.ws.
|
contains 1 rule |
Ensure Users Re-Authenticate for Privilege Escalation - sudo
[ref]ruleThe sudo NOPASSWD and !authenticate option, when
specified, allows a user to execute commands using sudo without having to
authenticate. This should be disabled by making sure that
NOPASSWD and/or !authenticate do not exist in
/etc/sudoers configuration file or any sudo configuration snippets
in /etc/sudoers.d/ ." Rationale:Without re-authentication, users may access resources or perform tasks for which they
do not have authorization.
When operating systems provide the capability to escalate a functional capability, it
is critical that the user re-authenticate. References:
5.3.4, 1, 12, 15, 16, 5, DSS05.04, DSS05.10, DSS06.03, DSS06.10, CCI-002038, CCI-004895, 4.3.3.5.1, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, A.18.1.4, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3, IA-11, CM-6(a), PR.AC-1, PR.AC-7, 2.2.6, 2.2, SRG-OS-000373-GPOS-00156, UBTU-22-432010, SV-260558r987879_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Find /etc/sudoers.d/ files
ansible.builtin.find:
paths:
- /etc/sudoers.d/
register: sudoers
tags:
- DISA-STIG-UBTU-22-432010
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-11
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.6
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sudo_require_authentication
- name: Remove lines containing NOPASSWD from sudoers files
ansible.builtin.replace:
regexp: (^(?!#).*[\s]+NOPASSWD[\s]*\:.*$)
replace: '# \g<1>'
path: '{{ item.path }}'
validate: /usr/sbin/visudo -cf %s
with_items:
- path: /etc/sudoers
- '{{ sudoers.files }}'
tags:
- DISA-STIG-UBTU-22-432010
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-11
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.6
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sudo_require_authentication
- name: Find /etc/sudoers.d/ files
ansible.builtin.find:
paths:
- /etc/sudoers.d/
register: sudoers
tags:
- DISA-STIG-UBTU-22-432010
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-11
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.6
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sudo_require_authentication
- name: Remove lines containing !authenticate from sudoers files
ansible.builtin.replace:
regexp: (^(?!#).*[\s]+\!authenticate.*$)
replace: '# \g<1>'
path: '{{ item.path }}'
validate: /usr/sbin/visudo -cf %s
with_items:
- path: /etc/sudoers
- '{{ sudoers.files }}'
tags:
- DISA-STIG-UBTU-22-432010
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-11
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.6
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sudo_require_authentication
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
for f in /etc/sudoers /etc/sudoers.d/* ; do
if [ ! -e "$f" ] ; then
continue
fi
matching_list=$(grep -P '^(?!#).*[\s]+NOPASSWD[\s]*\:.*$' $f | uniq )
if ! test -z "$matching_list"; then
while IFS= read -r entry; do
# comment out "NOPASSWD" matches to preserve user data
sed -i "s/^${entry}$/# &/g" $f
done <<< "$matching_list"
/usr/sbin/visudo -cf $f &> /dev/null || echo "Fail to validate $f with visudo"
fi
done
for f in /etc/sudoers /etc/sudoers.d/* ; do
if [ ! -e "$f" ] ; then
continue
fi
matching_list=$(grep -P '^(?!#).*[\s]+\!authenticate.*$' $f | uniq )
if ! test -z "$matching_list"; then
while IFS= read -r entry; do
# comment out "!authenticate" matches to preserve user data
sed -i "s/^${entry}$/# &/g" $f
done <<< "$matching_list"
/usr/sbin/visudo -cf $f &> /dev/null || echo "Fail to validate $f with visudo"
fi
done
|
Updating Software
[ref]groupThe apt_get command line tool is used to install and
update software packages. The system also provides a graphical
software update tool in the System menu, in the Administration submenu,
called Software Update.
Ubuntu 22.04 systems contain an installed software catalog called
the RPM database, which records metadata of installed packages. Consistently using
apt_get or the graphical Software Update for all software installation
allows for insight into the current inventory of installed software on the system.
|
contains 1 rule |
Ensure apt_get Removes Previous Package Versions
[ref]ruleapt_get should be configured to remove previous software components after
new versions have been installed. To configure apt_get to remove the
previous software components after updating, set the ::Remove-Unused-Dependencies and
::Remove-Unused-Kernel-Packages
to true in /etc/apt/apt.conf . Rationale:Previous versions of software components that are not removed from the information
system after updates have been installed may be exploited by some adversaries. References:
18, 20, 4, APO12.01, APO12.02, APO12.03, APO12.04, BAI03.10, DSS05.01, DSS05.02, 3.4.8, CCI-002617, 4.2.3, 4.2.3.12, 4.2.3.7, 4.2.3.9, A.12.6.1, A.14.2.3, A.16.1.3, A.18.2.2, A.18.2.3, SI-2(6), CM-11(a), CM-11(b), CM-6(a), ID.RA-1, PR.IP-12, SRG-OS-000437-GPOS-00194, UBTU-22-214015, SV-260477r958936_rule Remediation Shell script: (show)
flag1=1
flag2=1
for file in /etc/apt/apt.conf.d/*; do
if [ -e "$file" ]; then
if grep -qi "Unattended-Upgrade::Remove-Unused-Dependencies" $file; then
sed -i --follow-symlinks "s/^.*Unattended-Upgrade::Remove-Unused-Dependencies.*/Unattended-Upgrade::Remove-Unused-Dependencies \"true\";/I" $file
flag1=0
fi
if grep -qi "Unattended-Upgrade::Remove-Unused-Kernel-Packages" $file; then
sed -i --follow-symlinks "s/^.*Unattended-Upgrade::Remove-Unused-Kernel-Packages.*/Unattended-Upgrade::Remove-Unused-Kernel-Packages \"true\";/I" $file
flag2=0
fi
fi
done
if [ $flag1 ] || [ $flag2 ]; then
echo "Unattended-Upgrade::Remove-Unused-Dependencies \"true\";" >> /etc/apt/apt.conf.d/50unattended-upgrades
echo "Unattended-Upgrade::Remove-Unused-Kernel-Packages \"true\";" >> /etc/apt/apt.conf.d/50unattended-upgrades
fi
|
Account and Access Control
[ref]groupIn traditional Unix security, if an attacker gains
shell access to a certain login account, they can perform any action
or access any file to which that account has access. Therefore,
making it more difficult for unauthorized people to gain shell
access to accounts, particularly to privileged accounts, is a
necessary part of securing a system. This section introduces
mechanisms for restricting access to accounts under
Ubuntu 22.04. |
contains 43 rules |
Warning Banners for System Accesses
[ref]groupEach system should expose as little information about
itself as possible.
System banners, which are typically displayed just before a
login prompt, give out information about the service or the host's
operating system. This might include the distribution name and the
system kernel version, and the particular version of a network
service. This information can assist intruders in gaining access to
the system as it can reveal whether the system is running
vulnerable software. Most network services can be configured to
limit what information is displayed.
Many organizations implement security policies that require a
system banner provide notice of the system's ownership, provide
warning to unauthorized users, and remind authorized users of their
consent to monitoring. |
contains 3 rules |
Implement a GUI Warning Banner
[ref]groupIn the default graphical environment, users logging
directly into the system are greeted with a login screen provided
by the GNOME Display Manager (GDM). The warning banner should be
displayed in this graphical environment for these users.
The following sections describe how to configure the GDM login
banner. |
contains 2 rules |
Enable GNOME3 Login Warning Banner
[ref]ruleIn the default graphical environment, displaying a login warning banner
in the GNOME Display Manager's login screen can be enabled on the login
screen by setting banner-message-enable to true .
To enable, add or edit banner-message-enable to
/etc/dconf/db/gdm.d/00-security-settings . For example:
[org/gnome/login-screen]
banner-message-enable=true
Once the setting has been added, add a lock to
/etc/dconf/db/gdm.d/locks/00-security-settings-lock to prevent user modification.
For example:
/org/gnome/login-screen/banner-message-enable
After the settings have been set, run dconf update .
The banner text must also be set.Rationale:Display of a standardized and approved use notification before granting access to the operating system
ensures privacy and security notification verbiage used is consistent with applicable federal laws,
Executive Orders, directives, policies, regulations, standards, and guidance.
For U.S. Government systems, system use notifications are required only for access via login interfaces
with human users and are not required when such human interfaces do not exist. References:
1.8.2, 1, 12, 15, 16, DSS05.04, DSS05.10, DSS06.10, 3.1.9, CCI-001387, CCI-001384, CCI-000048, CCI-001386, CCI-001388, CCI-001385, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9, A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3, AC-8(a), AC-8(b), AC-8(c), PR.AC-7, SRG-OS-000023-GPOS-00006, SRG-OS-000228-GPOS-00088, UBTU-22-271010, SV-260535r958390_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'gdm3' 2>/dev/null | grep -q '^installed'; then
mkdir -p /etc/dconf/profile
dconf_profile_path=/etc/dconf/profile/user
[[ -s "${dconf_profile_path}" ]] || echo > "${dconf_profile_path}"
if ! grep -Pzq "(?s)^\s*user-db:user.*\n\s*system-db:local" "${dconf_profile_path}"; then
sed -i --follow-symlinks "1s/^/user-db:user\nsystem-db:local\n/" "${dconf_profile_path}"
fi
# Make sure the corresponding directories exist
mkdir -p /etc/dconf/db/local.d
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/profile
(umask 0022 && dconf update)
mkdir -p /etc/dconf/profile
dconf_profile_path=/etc/dconf/profile/gdm
[[ -s "${dconf_profile_path}" ]] || echo > "${dconf_profile_path}"
if ! grep -Pzq "(?s)^\s*user-db:user.*\n\s*system-db:gdm" "${dconf_profile_path}"; then
sed -i --follow-symlinks "1s/^/user-db:user\nsystem-db:gdm\n/" "${dconf_profile_path}"
fi
# Make sure the corresponding directories exist
mkdir -p /etc/dconf/db/gdm.d
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/profile
(umask 0022 && dconf update)
# Duplicate the setting also in 'greeter.dconf-defaults' for consistency with
# 'dconf_gnome_login_banner_text' and better alignment with STIG V1R1.
if [ -e "/etc/gdm3/greeter.dconf-defaults" ] ; then
LC_ALL=C sed -i "/^\s*banner\-message\-enable/Id" "/etc/gdm3/greeter.dconf-defaults"
else
touch "/etc/gdm3/greeter.dconf-defaults"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/gdm3/greeter.dconf-defaults"
cp "/etc/gdm3/greeter.dconf-defaults" "/etc/gdm3/greeter.dconf-defaults.bak"
# Insert after the line matching the regex '\[org/gnome/login-screen\]'
line_number="$(LC_ALL=C grep -n "\[org/gnome/login-screen\]" "/etc/gdm3/greeter.dconf-defaults.bak" | LC_ALL=C sed 's/:.*//g')"
if [ -z "$line_number" ]; then
# There was no match of '\[org/gnome/login-screen\]', insert at
# the end of the file.
printf '%s\n' "banner-message-enable=true" >> "/etc/gdm3/greeter.dconf-defaults"
else
head -n "$(( line_number ))" "/etc/gdm3/greeter.dconf-defaults.bak" > "/etc/gdm3/greeter.dconf-defaults"
printf '%s\n' "banner-message-enable=true" >> "/etc/gdm3/greeter.dconf-defaults"
tail -n "+$(( line_number + 1 ))" "/etc/gdm3/greeter.dconf-defaults.bak" >> "/etc/gdm3/greeter.dconf-defaults"
fi
# Clean up after ourselves.
rm "/etc/gdm3/greeter.dconf-defaults.bak"
# Check for setting in any of the DConf db directories
# If files contain ibus or distro, ignore them.
# The assignment assumes that individual filenames don't contain :
readarray -t SETTINGSFILES < <(grep -r "\\[org/gnome/login-screen\\]" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|gdm.d' | cut -d":" -f1)
DCONFFILE="/etc/dconf/db/gdm.d/00-security-settings"
DBDIR="/etc/dconf/db/gdm.d"
mkdir -p "${DBDIR}"
# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
if grep -q "^\\s*banner-message-enable\\s*=" "${SETTINGSFILES[@]}"
then
sed -Ei "s/(^\s*)banner-message-enable(\s*=)/#\1banner-message-enable\2/g" "${SETTINGSFILES[@]}"
fi
fi
[ ! -z "${DCONFFILE}" ] && echo "" >> "${DCONFFILE}"
if ! grep -q "\\[org/gnome/login-screen\\]" "${DCONFFILE}"
then
printf '%s\n' "[org/gnome/login-screen]" >> ${DCONFFILE}
fi
escaped_value="$(sed -e 's/\\/\\\\/g' <<< "true")"
if grep -q "^\\s*banner-message-enable\\s*=" "${DCONFFILE}"
then
sed -i "s/\\s*banner-message-enable\\s*=\\s*.*/banner-message-enable=${escaped_value}/g" "${DCONFFILE}"
else
sed -i "\\|\\[org/gnome/login-screen\\]|a\\banner-message-enable=${escaped_value}" "${DCONFFILE}"
fi
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/db
(umask 0022 && dconf update)
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/org/gnome/login-screen/banner-message-enable$" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|gdm.d' | grep ":" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/gdm.d/locks"
mkdir -p "${LOCKSFOLDER}"
# Comment out the configurations in databases different from the target one
if [[ ! -z "${LOCKFILES}" ]]
then
sed -i -E "s|^/org/gnome/login-screen/banner-message-enable$|#&|" "${LOCKFILES[@]}"
fi
if ! grep -qr "^/org/gnome/login-screen/banner-message-enable$" /etc/dconf/db/gdm.d/
then
echo "/org/gnome/login-screen/banner-message-enable" >> "/etc/dconf/db/gdm.d/locks/00-security-settings-lock"
fi
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/db
(umask 0022 && dconf update)
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Set the GNOME3 Login Warning Banner Text
[ref]ruleIn the default graphical environment, configuring the login warning banner text
in the GNOME Display Manager's login screen can be configured on the login
screen by setting banner-message-text to 'APPROVED_BANNER'
where APPROVED_BANNER is the approved banner for your environment.
To enable, add or edit banner-message-text to
/etc/gdm3/greeter.dconf-defaults . For example:
[org/gnome/login-screen]
banner-message-text='APPROVED_BANNER'
After the settings have been set, run dconf update .
When entering a warning banner that spans several lines, remember
to begin and end the string with ' and use \n for new lines.Rationale:An appropriate warning message reinforces policy awareness during the logon
process and facilitates possible legal action against attackers. References:
1.8.2, 1, 12, 15, 16, DSS05.04, DSS05.10, DSS06.10, 3.1.9, CCI-000048, CCI-001384, CCI-001385, CCI-001386, CCI-001387, CCI-001388, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9, A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3, AC-8(a), AC-8(c), PR.AC-7, SRG-OS-000023-GPOS-00006, SRG-OS-000228-GPOS-00088, UBTU-22-271015, SV-260536r958390_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'gdm3' 2>/dev/null | grep -q '^installed'; then
login_banner_text='^(You[\s\n]+are[\s\n]+accessing[\s\n]+a[\s\n]+U\.S\.[\s\n]+Government[\s\n]+\(USG\)[\s\n]+Information[\s\n]+System[\s\n]+\(IS\)[\s\n]+that[\s\n]+is[\s\n]+provided[\s\n]+for[\s\n]+USG\-authorized[\s\n]+use[\s\n]+only\.[\s\n]+By[\s\n]+using[\s\n]+this[\s\n]+IS[\s\n]+\(which[\s\n]+includes[\s\n]+any[\s\n]+device[\s\n]+attached[\s\n]+to[\s\n]+this[\s\n]+IS\),[\s\n]+you[\s\n]+consent[\s\n]+to[\s\n]+the[\s\n]+following[\s\n]+conditions\:(?:[\n]+|(?:\\n)+)\-The[\s\n]+USG[\s\n]+routinely[\s\n]+intercepts[\s\n]+and[\s\n]+monitors[\s\n]+communications[\s\n]+on[\s\n]+this[\s\n]+IS[\s\n]+for[\s\n]+purposes[\s\n]+including,[\s\n]+but[\s\n]+not[\s\n]+limited[\s\n]+to,[\s\n]+penetration[\s\n]+testing,[\s\n]+COMSEC[\s\n]+monitoring,[\s\n]+network[\s\n]+operations[\s\n]+and[\s\n]+defense,[\s\n]+personnel[\s\n]+misconduct[\s\n]+\(PM\),[\s\n]+law[\s\n]+enforcement[\s\n]+\(LE\),[\s\n]+and[\s\n]+counterintelligence[\s\n]+\(CI\)[\s\n]+investigations\.(?:[\n]+|(?:\\n)+)\-At[\s\n]+any[\s\n]+time,[\s\n]+the[\s\n]+USG[\s\n]+may[\s\n]+inspect[\s\n]+and[\s\n]+seize[\s\n]+data[\s\n]+stored[\s\n]+on[\s\n]+this[\s\n]+IS\.(?:[\n]+|(?:\\n)+)\-Communications[\s\n]+using,[\s\n]+or[\s\n]+data[\s\n]+stored[\s\n]+on,[\s\n]+this[\s\n]+IS[\s\n]+are[\s\n]+not[\s\n]+private,[\s\n]+are[\s\n]+subject[\s\n]+to[\s\n]+routine[\s\n]+monitoring,[\s\n]+interception,[\s\n]+and[\s\n]+search,[\s\n]+and[\s\n]+may[\s\n]+be[\s\n]+disclosed[\s\n]+or[\s\n]+used[\s\n]+for[\s\n]+any[\s\n]+USG\-authorized[\s\n]+purpose\.(?:[\n]+|(?:\\n)+)\-This[\s\n]+IS[\s\n]+includes[\s\n]+security[\s\n]+measures[\s\n]+\(e\.g\.,[\s\n]+authentication[\s\n]+and[\s\n]+access[\s\n]+controls\)[\s\n]+to[\s\n]+protect[\s\n]+USG[\s\n]+interests\-\-not[\s\n]+for[\s\n]+your[\s\n]+personal[\s\n]+benefit[\s\n]+or[\s\n]+privacy\.(?:[\n]+|(?:\\n)+)\-Notwithstanding[\s\n]+the[\s\n]+above,[\s\n]+using[\s\n]+this[\s\n]+IS[\s\n]+does[\s\n]+not[\s\n]+constitute[\s\n]+consent[\s\n]+to[\s\n]+PM,[\s\n]+LE[\s\n]+or[\s\n]+CI[\s\n]+investigative[\s\n]+searching[\s\n]+or[\s\n]+monitoring[\s\n]+of[\s\n]+the[\s\n]+content[\s\n]+of[\s\n]+privileged[\s\n]+communications,[\s\n]+or[\s\n]+work[\s\n]+product,[\s\n]+related[\s\n]+to[\s\n]+personal[\s\n]+representation[\s\n]+or[\s\n]+services[\s\n]+by[\s\n]+attorneys,[\s\n]+psychotherapists,[\s\n]+or[\s\n]+clergy,[\s\n]+and[\s\n]+their[\s\n]+assistants\.[\s\n]+Such[\s\n]+communications[\s\n]+and[\s\n]+work[\s\n]+product[\s\n]+are[\s\n]+private[\s\n]+and[\s\n]+confidential\.[\s\n]+See[\s\n]+User[\s\n]+Agreement[\s\n]+for[\s\n]+details\.|I've[\s\n]+read[\s\n]+\&[\s\n]+consent[\s\n]+to[\s\n]+terms[\s\n]+in[\s\n]+IS[\s\n]+user[\s\n]+agreem't\.)$'
# Multiple regexes transform the banner regex into a usable banner
# 0 - Remove anchors around the banner text
login_banner_text=$(echo "$login_banner_text" | sed 's/^\^\(.*\)\$$/\1/g')
# 1 - Keep only the first banners if there are multiple
# (dod_banners contains the long and short banner)
login_banner_text=$(echo "$login_banner_text" | sed 's/^(\(.*\.\)|.*)$/\1/g')
# 2 - Add spaces ' '. (Transforms regex for "space or newline" into a " ")
login_banner_text=$(echo "$login_banner_text" | sed 's/\[\\s\\n\]+/ /g')
# 3 - Adds newline "tokens". (Transforms "(?:\[\\n\]+|(?:\\n)+)" into "(n)*")
login_banner_text=$(echo "$login_banner_text" | sed 's/(?:\[\\n\]+|(?:\\\\n)+)/(n)*/g')
# 4 - Remove any leftover backslash. (From any parethesis in the banner, for example).
login_banner_text=$(echo "$login_banner_text" | sed 's/\\//g')
# 5 - Removes the newline "token." (Transforms them into newline escape sequences "\n").
# ( Needs to be done after 4, otherwise the escapce sequence will become just "n".
login_banner_text=$(echo "$login_banner_text" | sed 's/(n)\*/\\n/g')
mkdir -p /etc/dconf/profile
dconf_profile_path=/etc/dconf/profile/user
[[ -s "${dconf_profile_path}" ]] || echo > "${dconf_profile_path}"
if ! grep -Pzq "(?s)^\s*user-db:user.*\n\s*system-db:local" "${dconf_profile_path}"; then
sed -i --follow-symlinks "1s/^/user-db:user\nsystem-db:local\n/" "${dconf_profile_path}"
fi
# Make sure the corresponding directories exist
mkdir -p /etc/dconf/db/local.d
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/profile
(umask 0022 && dconf update)
mkdir -p /etc/dconf/profile
dconf_profile_path=/etc/dconf/profile/gdm
[[ -s "${dconf_profile_path}" ]] || echo > "${dconf_profile_path}"
if ! grep -Pzq "(?s)^\s*user-db:user.*\n\s*system-db:gdm" "${dconf_profile_path}"; then
sed -i --follow-symlinks "1s/^/user-db:user\nsystem-db:gdm\n/" "${dconf_profile_path}"
fi
# Make sure the corresponding directories exist
mkdir -p /etc/dconf/db/gdm.d
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/profile
(umask 0022 && dconf update)
# Will do both approach, since we plan to migrate to checks over dconf db. That way, future updates of the tool
# will pass the check even if we decide to check only for the dconf db path.
if [ -e "/etc/gdm3/greeter.dconf-defaults" ] ; then
LC_ALL=C sed -i "/^\s*banner\-message\-text/Id" "/etc/gdm3/greeter.dconf-defaults"
else
touch "/etc/gdm3/greeter.dconf-defaults"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/gdm3/greeter.dconf-defaults"
cp "/etc/gdm3/greeter.dconf-defaults" "/etc/gdm3/greeter.dconf-defaults.bak"
# Insert after the line matching the regex '\[org/gnome/login-screen\]'
line_number="$(LC_ALL=C grep -n "\[org/gnome/login-screen\]" "/etc/gdm3/greeter.dconf-defaults.bak" | LC_ALL=C sed 's/:.*//g')"
if [ -z "$line_number" ]; then
# There was no match of '\[org/gnome/login-screen\]', insert at
# the end of the file.
printf '%s\n' "banner-message-text='${login_banner_text}'" >> "/etc/gdm3/greeter.dconf-defaults"
else
head -n "$(( line_number ))" "/etc/gdm3/greeter.dconf-defaults.bak" > "/etc/gdm3/greeter.dconf-defaults"
printf '%s\n' "banner-message-text='${login_banner_text}'" >> "/etc/gdm3/greeter.dconf-defaults"
tail -n "+$(( line_number + 1 ))" "/etc/gdm3/greeter.dconf-defaults.bak" >> "/etc/gdm3/greeter.dconf-defaults"
fi
# Clean up after ourselves.
rm "/etc/gdm3/greeter.dconf-defaults.bak"
# Check for setting in any of the DConf db directories
# If files contain ibus or distro, ignore them.
# The assignment assumes that individual filenames don't contain :
readarray -t SETTINGSFILES < <(grep -r "\\[org/gnome/login-screen\\]" "/etc/dconf/db/" \
| grep -v 'distro\|ibus\|gdm.d' | cut -d":" -f1)
DCONFFILE="/etc/dconf/db/gdm.d/00-security-settings"
DBDIR="/etc/dconf/db/gdm.d"
mkdir -p "${DBDIR}"
# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
if grep -q "^\\s*banner-message-text\\s*=" "${SETTINGSFILES[@]}"
then
sed -Ei "s/(^\s*)banner-message-text(\s*=)/#\1banner-message-text\2/g" "${SETTINGSFILES[@]}"
fi
fi
[ ! -z "${DCONFFILE}" ] && echo "" >> "${DCONFFILE}"
if ! grep -q "\\[org/gnome/login-screen\\]" "${DCONFFILE}"
then
printf '%s\n' "[org/gnome/login-screen]" >> ${DCONFFILE}
fi
escaped_value="$(sed -e 's/\\/\\\\/g' <<< "'${login_banner_text}'")"
if grep -q "^\\s*banner-message-text\\s*=" "${DCONFFILE}"
then
sed -i "s/\\s*banner-message-text\\s*=\\s*.*/banner-message-text=${escaped_value}/g" "${DCONFFILE}"
else
sed -i "\\|\\[org/gnome/login-screen\\]|a\\banner-message-text=${escaped_value}" "${DCONFFILE}"
fi
# Make sure permissions allow regular users to read dconf settings.
# Also define the umask to avoid `dconf update` changing permissions.
chmod -R u=rwX,go=rX /etc/dconf/db
(umask 0022 && dconf update)
# No need to use dconf update, since bash_dconf_settings does that already
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Modify the System Login Banner for Remote Connections
[ref]ruleTo configure the system login banner edit /etc/issue.net . Replace the
default text with a message compliant with the local site policy or a legal
disclaimer.
The DoD required text is either:
You are accessing a U.S. Government (USG) Information System (IS) that
is provided for USG-authorized use only. By using this IS (which includes
any device attached to this IS), you consent to the following conditions:
-The USG routinely intercepts and monitors communications on this IS
for purposes including, but not limited to, penetration testing, COMSEC
monitoring, network operations and defense, personnel misconduct (PM), law
enforcement (LE), and counterintelligence (CI) investigations.
-At any time, the USG may inspect and seize data stored on this IS.
-Communications using, or data stored on, this IS are not private,
are subject to routine monitoring, interception, and search, and may be
disclosed or used for any USG-authorized purpose.
-This IS includes security measures (e.g., authentication and access
controls) to protect USG interests -- not for your personal benefit or
privacy.
-Notwithstanding the above, using this IS does not constitute consent
to PM, LE or CI investigative searching or monitoring of the content of
privileged communications, or work product, related to personal
representation or services by attorneys, psychotherapists, or clergy, and
their assistants. Such communications and work product are private and
confidential. See User Agreement for details.
OR:
I've read & consent to terms in IS user agreem't. Rationale:Display of a standardized and approved use notification before granting
access to the operating system ensures privacy and security notification
verbiage used is consistent with applicable federal laws, Executive Orders,
directives, policies, regulations, standards, and guidance.
System use notifications are required only for access via login interfaces
with human users and are not required when such human interfaces do not
exist. References:
1.7.3, CCI-000048, CCI-001384, CCI-001385, CCI-001386, CCI-001387, CCI-001388, SRG-OS-000023-GPOS-00006, SRG-OS-000228-GPOS-00088, UBTU-22-255020, SV-260525r958390_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | false |
---|
Strategy: | unknown |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-255020
- banner_etc_issue_net
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- unknown_strategy
- name: XCCDF Value remote_login_banner_text # promote to variable
set_fact:
remote_login_banner_text: !!str ^(You[\s\n]+are[\s\n]+accessing[\s\n]+a[\s\n]+U\.S\.[\s\n]+Government[\s\n]+\(USG\)[\s\n]+Information[\s\n]+System[\s\n]+\(IS\)[\s\n]+that[\s\n]+is[\s\n]+provided[\s\n]+for[\s\n]+USG\-authorized[\s\n]+use[\s\n]+only\.[\s\n]+By[\s\n]+using[\s\n]+this[\s\n]+IS[\s\n]+\(which[\s\n]+includes[\s\n]+any[\s\n]+device[\s\n]+attached[\s\n]+to[\s\n]+this[\s\n]+IS\),[\s\n]+you[\s\n]+consent[\s\n]+to[\s\n]+the[\s\n]+following[\s\n]+conditions\:(?:[\n]+|(?:\\n)+)\-The[\s\n]+USG[\s\n]+routinely[\s\n]+intercepts[\s\n]+and[\s\n]+monitors[\s\n]+communications[\s\n]+on[\s\n]+this[\s\n]+IS[\s\n]+for[\s\n]+purposes[\s\n]+including,[\s\n]+but[\s\n]+not[\s\n]+limited[\s\n]+to,[\s\n]+penetration[\s\n]+testing,[\s\n]+COMSEC[\s\n]+monitoring,[\s\n]+network[\s\n]+operations[\s\n]+and[\s\n]+defense,[\s\n]+personnel[\s\n]+misconduct[\s\n]+\(PM\),[\s\n]+law[\s\n]+enforcement[\s\n]+\(LE\),[\s\n]+and[\s\n]+counterintelligence[\s\n]+\(CI\)[\s\n]+investigations\.(?:[\n]+|(?:\\n)+)\-At[\s\n]+any[\s\n]+time,[\s\n]+the[\s\n]+USG[\s\n]+may[\s\n]+inspect[\s\n]+and[\s\n]+seize[\s\n]+data[\s\n]+stored[\s\n]+on[\s\n]+this[\s\n]+IS\.(?:[\n]+|(?:\\n)+)\-Communications[\s\n]+using,[\s\n]+or[\s\n]+data[\s\n]+stored[\s\n]+on,[\s\n]+this[\s\n]+IS[\s\n]+are[\s\n]+not[\s\n]+private,[\s\n]+are[\s\n]+subject[\s\n]+to[\s\n]+routine[\s\n]+monitoring,[\s\n]+interception,[\s\n]+and[\s\n]+search,[\s\n]+and[\s\n]+may[\s\n]+be[\s\n]+disclosed[\s\n]+or[\s\n]+used[\s\n]+for[\s\n]+any[\s\n]+USG\-authorized[\s\n]+purpose\.(?:[\n]+|(?:\\n)+)\-This[\s\n]+IS[\s\n]+includes[\s\n]+security[\s\n]+measures[\s\n]+\(e\.g\.,[\s\n]+authentication[\s\n]+and[\s\n]+access[\s\n]+controls\)[\s\n]+to[\s\n]+protect[\s\n]+USG[\s\n]+interests\-\-not[\s\n]+for[\s\n]+your[\s\n]+personal[\s\n]+benefit[\s\n]+or[\s\n]+privacy\.(?:[\n]+|(?:\\n)+)\-Notwithstanding[\s\n]+the[\s\n]+above,[\s\n]+using[\s\n]+this[\s\n]+IS[\s\n]+does[\s\n]+not[\s\n]+constitute[\s\n]+consent[\s\n]+to[\s\n]+PM,[\s\n]+LE[\s\n]+or[\s\n]+CI[\s\n]+investigative[\s\n]+searching[\s\n]+or[\s\n]+monitoring[\s\n]+of[\s\n]+the[\s\n]+content[\s\n]+of[\s\n]+privileged[\s\n]+communications,[\s\n]+or[\s\n]+work[\s\n]+product,[\s\n]+related[\s\n]+to[\s\n]+personal[\s\n]+representation[\s\n]+or[\s\n]+services[\s\n]+by[\s\n]+attorneys,[\s\n]+psychotherapists,[\s\n]+or[\s\n]+clergy,[\s\n]+and[\s\n]+their[\s\n]+assistants\.[\s\n]+Such[\s\n]+communications[\s\n]+and[\s\n]+work[\s\n]+product[\s\n]+are[\s\n]+private[\s\n]+and[\s\n]+confidential\.[\s\n]+See[\s\n]+User[\s\n]+Agreement[\s\n]+for[\s\n]+details\.|I've[\s\n]+read[\s\n]+\&[\s\n]+consent[\s\n]+to[\s\n]+terms[\s\n]+in[\s\n]+IS[\s\n]+user[\s\n]+agreem't\.)$
tags:
- always
- name: Modify the System Login Banner for Remote Connections - ensure correct banner
copy:
dest: /etc/issue.net
content: '{{ remote_login_banner_text | regex_replace("^\^(.*)\$$", "\1") | regex_replace("^\((.*\.)\|.*\)$",
"\1") | regex_replace("\[\\s\\n\]\+"," ") | regex_replace("\(\?:\[\\n\]\+\|\(\?:\\\\n\)\+\)",
"\n") | regex_replace("\\", "") | wordwrap() }}'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-255020
- banner_etc_issue_net
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- unknown_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
remote_login_banner_text='^(You[\s\n]+are[\s\n]+accessing[\s\n]+a[\s\n]+U\.S\.[\s\n]+Government[\s\n]+\(USG\)[\s\n]+Information[\s\n]+System[\s\n]+\(IS\)[\s\n]+that[\s\n]+is[\s\n]+provided[\s\n]+for[\s\n]+USG\-authorized[\s\n]+use[\s\n]+only\.[\s\n]+By[\s\n]+using[\s\n]+this[\s\n]+IS[\s\n]+\(which[\s\n]+includes[\s\n]+any[\s\n]+device[\s\n]+attached[\s\n]+to[\s\n]+this[\s\n]+IS\),[\s\n]+you[\s\n]+consent[\s\n]+to[\s\n]+the[\s\n]+following[\s\n]+conditions\:(?:[\n]+|(?:\\n)+)\-The[\s\n]+USG[\s\n]+routinely[\s\n]+intercepts[\s\n]+and[\s\n]+monitors[\s\n]+communications[\s\n]+on[\s\n]+this[\s\n]+IS[\s\n]+for[\s\n]+purposes[\s\n]+including,[\s\n]+but[\s\n]+not[\s\n]+limited[\s\n]+to,[\s\n]+penetration[\s\n]+testing,[\s\n]+COMSEC[\s\n]+monitoring,[\s\n]+network[\s\n]+operations[\s\n]+and[\s\n]+defense,[\s\n]+personnel[\s\n]+misconduct[\s\n]+\(PM\),[\s\n]+law[\s\n]+enforcement[\s\n]+\(LE\),[\s\n]+and[\s\n]+counterintelligence[\s\n]+\(CI\)[\s\n]+investigations\.(?:[\n]+|(?:\\n)+)\-At[\s\n]+any[\s\n]+time,[\s\n]+the[\s\n]+USG[\s\n]+may[\s\n]+inspect[\s\n]+and[\s\n]+seize[\s\n]+data[\s\n]+stored[\s\n]+on[\s\n]+this[\s\n]+IS\.(?:[\n]+|(?:\\n)+)\-Communications[\s\n]+using,[\s\n]+or[\s\n]+data[\s\n]+stored[\s\n]+on,[\s\n]+this[\s\n]+IS[\s\n]+are[\s\n]+not[\s\n]+private,[\s\n]+are[\s\n]+subject[\s\n]+to[\s\n]+routine[\s\n]+monitoring,[\s\n]+interception,[\s\n]+and[\s\n]+search,[\s\n]+and[\s\n]+may[\s\n]+be[\s\n]+disclosed[\s\n]+or[\s\n]+used[\s\n]+for[\s\n]+any[\s\n]+USG\-authorized[\s\n]+purpose\.(?:[\n]+|(?:\\n)+)\-This[\s\n]+IS[\s\n]+includes[\s\n]+security[\s\n]+measures[\s\n]+\(e\.g\.,[\s\n]+authentication[\s\n]+and[\s\n]+access[\s\n]+controls\)[\s\n]+to[\s\n]+protect[\s\n]+USG[\s\n]+interests\-\-not[\s\n]+for[\s\n]+your[\s\n]+personal[\s\n]+benefit[\s\n]+or[\s\n]+privacy\.(?:[\n]+|(?:\\n)+)\-Notwithstanding[\s\n]+the[\s\n]+above,[\s\n]+using[\s\n]+this[\s\n]+IS[\s\n]+does[\s\n]+not[\s\n]+constitute[\s\n]+consent[\s\n]+to[\s\n]+PM,[\s\n]+LE[\s\n]+or[\s\n]+CI[\s\n]+investigative[\s\n]+searching[\s\n]+or[\s\n]+monitoring[\s\n]+of[\s\n]+the[\s\n]+content[\s\n]+of[\s\n]+privileged[\s\n]+communications,[\s\n]+or[\s\n]+work[\s\n]+product,[\s\n]+related[\s\n]+to[\s\n]+personal[\s\n]+representation[\s\n]+or[\s\n]+services[\s\n]+by[\s\n]+attorneys,[\s\n]+psychotherapists,[\s\n]+or[\s\n]+clergy,[\s\n]+and[\s\n]+their[\s\n]+assistants\.[\s\n]+Such[\s\n]+communications[\s\n]+and[\s\n]+work[\s\n]+product[\s\n]+are[\s\n]+private[\s\n]+and[\s\n]+confidential\.[\s\n]+See[\s\n]+User[\s\n]+Agreement[\s\n]+for[\s\n]+details\.|I've[\s\n]+read[\s\n]+\&[\s\n]+consent[\s\n]+to[\s\n]+terms[\s\n]+in[\s\n]+IS[\s\n]+user[\s\n]+agreem't\.)$'
# Multiple regexes transform the banner regex into a usable banner
# 0 - Remove anchors around the banner text
remote_login_banner_text=$(echo "$remote_login_banner_text" | sed 's/^\^\(.*\)\$$/\1/g')
# 1 - Keep only the first banners if there are multiple
# (dod_banners contains the long and short banner)
remote_login_banner_text=$(echo "$remote_login_banner_text" | sed 's/^(\(.*\.\)|.*)$/\1/g')
# 2 - Add spaces ' '. (Transforms regex for "space or newline" into a " ")
remote_login_banner_text=$(echo "$remote_login_banner_text" | sed 's/\[\\s\\n\]+/ /g')
# 3 - Adds newlines. (Transforms "(?:\[\\n\]+|(?:\\n)+)" into "\n")
remote_login_banner_text=$(echo "$remote_login_banner_text" | sed 's/(?:\[\\n\]+|(?:\\\\n)+)/\n/g')
# 4 - Remove any leftover backslash. (From any parethesis in the banner, for example).
remote_login_banner_text=$(echo "$remote_login_banner_text" | sed 's/\\//g')
formatted=$(echo "$remote_login_banner_text" | fold -sw 80)
cat <<EOF >/etc/issue.net
$formatted
EOF
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Protect Accounts by Configuring PAM
[ref]groupPAM, or Pluggable Authentication Modules, is a system
which implements modular authentication for Linux programs. PAM provides
a flexible and configurable architecture for authentication, and it should be configured
to minimize exposure to unnecessary risk. This section contains
guidance on how to accomplish that.
PAM is implemented as a set of shared objects which are
loaded and invoked whenever an application wishes to authenticate a
user. Typically, the application must be running as root in order
to take advantage of PAM, because PAM's modules often need to be able
to access sensitive stores of account information, such as /etc/shadow.
Traditional privileged network listeners
(e.g. sshd) or SUID programs (e.g. sudo) already meet this
requirement. An SUID root application, userhelper, is provided so
that programs which are not SUID or privileged themselves can still
take advantage of PAM.
PAM looks in the directory /etc/pam.d for
application-specific configuration information. For instance, if
the program login attempts to authenticate a user, then PAM's
libraries follow the instructions in the file /etc/pam.d/login
to determine what actions should be taken.
One very important file in /etc/pam.d is
/etc/pam.d/system-auth . This file, which is included by
many other PAM configuration files, defines 'default' system authentication
measures. Modifying this file is a good way to make far-reaching
authentication changes, for instance when implementing a
centralized authentication service. Warning:
Be careful when making changes to PAM's configuration files.
The syntax for these files is complex, and modifications can
have unexpected consequences. The default configurations shipped
with applications should be sufficient for most users. |
contains 19 rules |
Set Lockouts for Failed Password Attempts
[ref]groupThe pam_faillock PAM module provides the capability to
lock out user accounts after a number of failed login attempts. Its
documentation is available in
/usr/share/doc/pam-VERSION/txts/README.pam_faillock .
Warning:
Locking out user accounts presents the
risk of a denial-of-service attack. The lockout policy
must weigh whether the risk of such a
denial-of-service attack outweighs the benefits of thwarting
password guessing attacks. |
contains 6 rules |
Enforce Delay After Failed Logon Attempts
[ref]ruleTo configure the system to introduce a delay after failed logon attempts,
add or correct the pam_faildelay settings in
/etc/pam.d/common-auth to make sure its delay parameter
is at least 4000000 or greater. For example:
auth required pam_faildelay.so delay=4000000 Rationale:Limiting the number of logon attempts over a certain time interval reduces
the chances that an unauthorized user may gain access to an account. Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
var_password_pam_delay='4000000'
if ! grep -qP "^\s*auth\s+required\s+pam_faildelay.so\s*.*" "/etc/pam.d/common-auth"; then
# Line matching group + control + module was not found. Check group + module.
if [ "$(grep -cP '^\s*auth\s+.*\s+pam_faildelay.so\s*' "/etc/pam.d/common-auth")" -eq 1 ]; then
# The control is updated only if one single line matches.
sed -i -E --follow-symlinks "s/^(\s*auth\s+).*(\bpam_faildelay.so.*)/\1required \2/" "/etc/pam.d/common-auth"
else
sed -i --follow-symlinks "1i auth required pam_faildelay.so" "/etc/pam.d/common-auth"
fi
fi
# Check the option
if ! grep -qP "^\s*auth\s+required\s+pam_faildelay.so\s*.*\sdelay\b" "/etc/pam.d/common-auth"; then
sed -i -E --follow-symlinks "/\s*auth\s+required\s+pam_faildelay.so.*/ s/$/ delay=$var_password_pam_delay/" "/etc/pam.d/common-auth"
else
sed -i -E --follow-symlinks "s/(\s*auth\s+required\s+pam_faildelay.so\s+.*)(delay=)[[:alnum:]]+\s*(.*)/\1\2$var_password_pam_delay \3/" "/etc/pam.d/common-auth"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Account Lockouts Must Be Logged
[ref]rulePAM faillock locks an account due to excessive password failures, this event must be logged. Rationale:Without auditing of these events it may be harder or impossible to identify what an attacker did after an attack. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Account Lockouts Must Be Logged - Check if system relies on authselect tool
ansible.builtin.stat:
path: /usr/bin/authselect
register: result_authselect_present
tags:
- DISA-STIG-UBTU-22-411045
- NIST-800-53-AC-7 (a)
- accounts_passwords_pam_faillock_audit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Account Lockouts Must Be Logged - Remediation where authselect tool is present
block:
- name: Account Lockouts Must Be Logged - Check integrity of authselect current
profile
ansible.builtin.command:
cmd: authselect check
register: result_authselect_check_cmd
changed_when: false
failed_when: false
- name: Account Lockouts Must Be Logged - Informative message based on the authselect
integrity check result
ansible.builtin.assert:
that:
- result_authselect_check_cmd.rc == 0
fail_msg:
- authselect integrity check failed. Remediation aborted!
- This remediation could not be applied because an authselect profile was not
selected or the selected profile is not intact.
- It is not recommended to manually edit the PAM files when authselect tool
is available.
- In cases where the default authselect profile does not cover a specific demand,
a custom authselect profile is recommended.
success_msg:
- authselect integrity check passed
- name: Account Lockouts Must Be Logged - Get authselect current features
ansible.builtin.shell:
cmd: authselect current | tail -n+3 | awk '{ print $2 }'
register: result_authselect_features
changed_when: false
when:
- result_authselect_check_cmd is success
- name: Account Lockouts Must Be Logged - Ensure "with-faillock" feature is enabled
using authselect tool
ansible.builtin.command:
cmd: authselect enable-feature with-faillock
register: result_authselect_enable_feature_cmd
when:
- result_authselect_check_cmd is success
- result_authselect_features.stdout is not search("with-faillock")
- name: Account Lockouts Must Be Logged - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b
when:
- result_authselect_enable_feature_cmd is not skipped
- result_authselect_enable_feature_cmd is success
when: result_authselect_present.stat.exists
tags:
- DISA-STIG-UBTU-22-411045
- NIST-800-53-AC-7 (a)
- accounts_passwords_pam_faillock_audit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Account Lockouts Must Be Logged - Remediation where authselect tool is not
present
block:
- name: Account Lockouts Must Be Logged - Check if pam_faillock.so is already enabled
ansible.builtin.lineinfile:
path: /etc/pam.d/system-auth
regexp: .*auth.*pam_faillock\.so (preauth|authfail)
state: absent
check_mode: true
changed_when: false
register: result_pam_faillock_is_enabled
- name: Account Lockouts Must Be Logged - Enable pam_faillock.so preauth editing
PAM files
ansible.builtin.lineinfile:
path: '{{ item }}'
line: auth required pam_faillock.so preauth
insertbefore: ^auth.*sufficient.*pam_unix\.so.*
state: present
loop:
- /etc/pam.d/system-auth
- /etc/pam.d/password-auth
when:
- result_pam_faillock_is_enabled.found == 0
- name: Account Lockouts Must Be Logged - Enable pam_faillock.so authfail editing
PAM files
ansible.builtin.lineinfile:
path: '{{ item }}'
line: auth required pam_faillock.so authfail
insertbefore: ^auth.*required.*pam_deny\.so.*
state: present
loop:
- /etc/pam.d/system-auth
- /etc/pam.d/password-auth
when:
- result_pam_faillock_is_enabled.found == 0
- name: Account Lockouts Must Be Logged - Enable pam_faillock.so account section
editing PAM files
ansible.builtin.lineinfile:
path: '{{ item }}'
line: account required pam_faillock.so
insertbefore: ^account.*required.*pam_unix\.so.*
state: present
loop:
- /etc/pam.d/system-auth
- /etc/pam.d/password-auth
when:
- result_pam_faillock_is_enabled.found == 0
when: not result_authselect_present.stat.exists
tags:
- DISA-STIG-UBTU-22-411045
- NIST-800-53-AC-7 (a)
- accounts_passwords_pam_faillock_audit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Account Lockouts Must Be Logged - Check the presence of /etc/security/faillock.conf
file
ansible.builtin.stat:
path: /etc/security/faillock.conf
register: result_faillock_conf_check
tags:
- DISA-STIG-UBTU-22-411045
- NIST-800-53-AC-7 (a)
- accounts_passwords_pam_faillock_audit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Account Lockouts Must Be Logged - Ensure the pam_faillock.so audit parameter
in /etc/security/faillock.conf
ansible.builtin.lineinfile:
path: /etc/security/faillock.conf
regexp: ^\s*audit
line: audit
state: present
when: result_faillock_conf_check.stat.exists
tags:
- DISA-STIG-UBTU-22-411045
- NIST-800-53-AC-7 (a)
- accounts_passwords_pam_faillock_audit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Account Lockouts Must Be Logged - Ensure the pam_faillock.so audit parameter
not in PAM files
block:
- name: Account Lockouts Must Be Logged - Check if /etc/pam.d/system-auth file is
present
ansible.builtin.stat:
path: /etc/pam.d/system-auth
register: result_pam_file_present
- name: Account Lockouts Must Be Logged - Check the proper remediation for the system
block:
- name: Account Lockouts Must Be Logged - Define the PAM file to be edited as
a local fact
ansible.builtin.set_fact:
pam_file_path: /etc/pam.d/system-auth
- name: Account Lockouts Must Be Logged - Check if system relies on authselect
tool
ansible.builtin.stat:
path: /usr/bin/authselect
register: result_authselect_present
- name: Account Lockouts Must Be Logged - Ensure authselect custom profile is
used if authselect is present
block:
- name: Account Lockouts Must Be Logged - Check integrity of authselect current
profile
ansible.builtin.command:
cmd: authselect check
register: result_authselect_check_cmd
changed_when: false
failed_when: false
- name: Account Lockouts Must Be Logged - Informative message based on the authselect
integrity check result
ansible.builtin.assert:
that:
- result_authselect_check_cmd.rc == 0
fail_msg:
- authselect integrity check failed. Remediation aborted!
- This remediation could not be applied because an authselect profile was
not selected or the selected profile is not intact.
- It is not recommended to manually edit the PAM files when authselect tool
is available.
- In cases where the default authselect profile does not cover a specific
demand, a custom authselect profile is recommended.
success_msg:
- authselect integrity check passed
- name: Account Lockouts Must Be Logged - Get authselect current profile
ansible.builtin.shell:
cmd: authselect current -r | awk '{ print $1 }'
register: result_authselect_profile
changed_when: false
when:
- result_authselect_check_cmd is success
- name: Account Lockouts Must Be Logged - Define the current authselect profile
as a local fact
ansible.builtin.set_fact:
authselect_current_profile: '{{ result_authselect_profile.stdout }}'
authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
when:
- result_authselect_profile is not skipped
- result_authselect_profile.stdout is match("custom/")
- name: Account Lockouts Must Be Logged - Define the new authselect custom profile
as a local fact
ansible.builtin.set_fact:
authselect_current_profile: '{{ result_authselect_profile.stdout }}'
authselect_custom_profile: custom/hardening
when:
- result_authselect_profile is not skipped
- result_authselect_profile.stdout is not match("custom/")
- name: Account Lockouts Must Be Logged - Get authselect current features to
also enable them in the custom profile
ansible.builtin.shell:
cmd: authselect current | tail -n+3 | awk '{ print $2 }'
register: result_authselect_features
changed_when: false
when:
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- name: Account Lockouts Must Be Logged - Check if any custom profile with the
same name was already created
ansible.builtin.stat:
path: /etc/authselect/{{ authselect_custom_profile }}
register: result_authselect_custom_profile_present
changed_when: false
when:
- authselect_current_profile is not match("custom/")
- name: Account Lockouts Must Be Logged - Create an authselect custom profile
based on the current profile
ansible.builtin.command:
cmd: authselect create-profile hardening -b {{ authselect_current_profile
}}
when:
- result_authselect_check_cmd is success
- authselect_current_profile is not match("^(custom/|local)")
- not result_authselect_custom_profile_present.stat.exists
- name: Account Lockouts Must Be Logged - Create an authselect custom profile
based on sssd profile
ansible.builtin.command:
cmd: authselect create-profile hardening -b sssd
when:
- result_authselect_check_cmd is success
- authselect_current_profile is match("local")
- not result_authselect_custom_profile_present.stat.exists
- name: Account Lockouts Must Be Logged - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- authselect_custom_profile is not match(authselect_current_profile)
- name: Account Lockouts Must Be Logged - Ensure the authselect custom profile
is selected
ansible.builtin.command:
cmd: authselect select {{ authselect_custom_profile }}
register: result_pam_authselect_select_profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- authselect_custom_profile is not match(authselect_current_profile)
- name: Account Lockouts Must Be Logged - Restore the authselect features in
the custom profile
ansible.builtin.command:
cmd: authselect enable-feature {{ item }}
loop: '{{ result_authselect_features.stdout_lines }}'
register: result_pam_authselect_restore_features
when:
- result_authselect_profile is not skipped
- result_authselect_features is not skipped
- result_pam_authselect_select_profile is not skipped
- name: Account Lockouts Must Be Logged - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- result_pam_authselect_restore_features is not skipped
- name: Account Lockouts Must Be Logged - Change the PAM file to be edited according
to the custom authselect profile
ansible.builtin.set_fact:
pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
| basename }}
when:
- result_authselect_present.stat.exists
- name: Account Lockouts Must Be Logged - Define a fact for control already filtered
in case filters are used
ansible.builtin.set_fact:
pam_module_control: ''
- name: Account Lockouts Must Be Logged - Ensure the "audit" option from "pam_faillock.so"
is not present in {{ pam_file_path }}
ansible.builtin.replace:
dest: '{{ pam_file_path }}'
regexp: (.*auth.*pam_faillock.so.*)\baudit\b=?[0-9a-zA-Z]*(.*)
replace: \1\2
register: result_pam_option_removal
- name: Account Lockouts Must Be Logged - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b
when:
- result_authselect_present.stat.exists
- result_pam_option_removal is changed
when:
- result_pam_file_present.stat.exists
- name: Account Lockouts Must Be Logged - Check if /etc/pam.d/password-auth file
is present
ansible.builtin.stat:
path: /etc/pam.d/password-auth
register: result_pam_file_present
- name: Account Lockouts Must Be Logged - Check the proper remediation for the system
block:
- name: Account Lockouts Must Be Logged - Define the PAM file to be edited as
a local fact
ansible.builtin.set_fact:
pam_file_path: /etc/pam.d/password-auth
- name: Account Lockouts Must Be Logged - Check if system relies on authselect
tool
ansible.builtin.stat:
path: /usr/bin/authselect
register: result_authselect_present
- name: Account Lockouts Must Be Logged - Ensure authselect custom profile is
used if authselect is present
block:
- name: Account Lockouts Must Be Logged - Check integrity of authselect current
profile
ansible.builtin.command:
cmd: authselect check
register: result_authselect_check_cmd
changed_when: false
failed_when: false
- name: Account Lockouts Must Be Logged - Informative message based on the authselect
integrity check result
ansible.builtin.assert:
that:
- result_authselect_check_cmd.rc == 0
fail_msg:
- authselect integrity check failed. Remediation aborted!
- This remediation could not be applied because an authselect profile was
not selected or the selected profile is not intact.
- It is not recommended to manually edit the PAM files when authselect tool
is available.
- In cases where the default authselect profile does not cover a specific
demand, a custom authselect profile is recommended.
success_msg:
- authselect integrity check passed
- name: Account Lockouts Must Be Logged - Get authselect current profile
ansible.builtin.shell:
cmd: authselect current -r | awk '{ print $1 }'
register: result_authselect_profile
changed_when: false
when:
- result_authselect_check_cmd is success
- name: Account Lockouts Must Be Logged - Define the current authselect profile
as a local fact
ansible.builtin.set_fact:
authselect_current_profile: '{{ result_authselect_profile.stdout }}'
authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
when:
- result_authselect_profile is not skipped
- result_authselect_profile.stdout is match("custom/")
- name: Account Lockouts Must Be Logged - Define the new authselect custom profile
as a local fact
ansible.builtin.set_fact:
authselect_current_profile: '{{ result_authselect_profile.stdout }}'
authselect_custom_profile: custom/hardening
when:
- result_authselect_profile is not skipped
- result_authselect_profile.stdout is not match("custom/")
- name: Account Lockouts Must Be Logged - Get authselect current features to
also enable them in the custom profile
ansible.builtin.shell:
cmd: authselect current | tail -n+3 | awk '{ print $2 }'
register: result_authselect_features
changed_when: false
when:
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- name: Account Lockouts Must Be Logged - Check if any custom profile with the
same name was already created
ansible.builtin.stat:
path: /etc/authselect/{{ authselect_custom_profile }}
register: result_authselect_custom_profile_present
changed_when: false
when:
- authselect_current_profile is not match("custom/")
- name: Account Lockouts Must Be Logged - Create an authselect custom profile
based on the current profile
ansible.builtin.command:
cmd: authselect create-profile hardening -b {{ authselect_current_profile
}}
when:
- result_authselect_check_cmd is success
- authselect_current_profile is not match("^(custom/|local)")
- not result_authselect_custom_profile_present.stat.exists
- name: Account Lockouts Must Be Logged - Create an authselect custom profile
based on sssd profile
ansible.builtin.command:
cmd: authselect create-profile hardening -b sssd
when:
- result_authselect_check_cmd is success
- authselect_current_profile is match("local")
- not result_authselect_custom_profile_present.stat.exists
- name: Account Lockouts Must Be Logged - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- authselect_custom_profile is not match(authselect_current_profile)
- name: Account Lockouts Must Be Logged - Ensure the authselect custom profile
is selected
ansible.builtin.command:
cmd: authselect select {{ authselect_custom_profile }}
register: result_pam_authselect_select_profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- authselect_custom_profile is not match(authselect_current_profile)
- name: Account Lockouts Must Be Logged - Restore the authselect features in
the custom profile
ansible.builtin.command:
cmd: authselect enable-feature {{ item }}
loop: '{{ result_authselect_features.stdout_lines }}'
register: result_pam_authselect_restore_features
when:
- result_authselect_profile is not skipped
- result_authselect_features is not skipped
- result_pam_authselect_select_profile is not skipped
- name: Account Lockouts Must Be Logged - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- result_pam_authselect_restore_features is not skipped
- name: Account Lockouts Must Be Logged - Change the PAM file to be edited according
to the custom authselect profile
ansible.builtin.set_fact:
pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
| basename }}
when:
- result_authselect_present.stat.exists
- name: Account Lockouts Must Be Logged - Define a fact for control already filtered
in case filters are used
ansible.builtin.set_fact:
pam_module_control: ''
- name: Account Lockouts Must Be Logged - Ensure the "audit" option from "pam_faillock.so"
is not present in {{ pam_file_path }}
ansible.builtin.replace:
dest: '{{ pam_file_path }}'
regexp: (.*auth.*pam_faillock.so.*)\baudit\b=?[0-9a-zA-Z]*(.*)
replace: \1\2
register: result_pam_option_removal
- name: Account Lockouts Must Be Logged - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b
when:
- result_authselect_present.stat.exists
- result_pam_option_removal is changed
when:
- result_pam_file_present.stat.exists
when: result_faillock_conf_check.stat.exists
tags:
- DISA-STIG-UBTU-22-411045
- NIST-800-53-AC-7 (a)
- accounts_passwords_pam_faillock_audit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Account Lockouts Must Be Logged - Ensure the pam_faillock.so audit parameter
in PAM files
block:
- name: Account Lockouts Must Be Logged - Check if pam_faillock.so audit parameter
is already enabled in pam files
ansible.builtin.lineinfile:
path: /etc/pam.d/system-auth
regexp: .*auth.*pam_faillock\.so (preauth|authfail).*audit
state: absent
check_mode: true
changed_when: false
register: result_pam_faillock_audit_parameter_is_present
- name: Account Lockouts Must Be Logged - Ensure the inclusion of pam_faillock.so
preauth audit parameter in auth section
ansible.builtin.lineinfile:
path: '{{ item }}'
backrefs: true
regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so preauth.*)
line: \1required\3 audit
state: present
loop:
- /etc/pam.d/system-auth
- /etc/pam.d/password-auth
when:
- result_pam_faillock_audit_parameter_is_present.found == 0
when: not result_faillock_conf_check.stat.exists
tags:
- DISA-STIG-UBTU-22-411045
- NIST-800-53-AC-7 (a)
- accounts_passwords_pam_faillock_audit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
if [ -f /usr/bin/authselect ]; then
if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
authselect enable-feature with-faillock
authselect apply-changes -b
else
conf_name=cac_faillock
if [ ! -f /usr/share/pam-configs/"$conf_name" ]; then
cat << EOF > /usr/share/pam-configs/"$conf_name"
Name: Enable pam_faillock to deny access
Default: yes
Conflicts: faillock
Priority: 0
Auth-Type: Primary
Auth:
[default=die] pam_faillock.so authfail
EOF
fi
if [ ! -f /usr/share/pam-configs/"$conf_name"_notify ]; then
cat << EOF > /usr/share/pam-configs/"$conf_name"_notify
Name: Notify of failed login attempts and reset count upon success
Default: yes
Conflicts: faillock_notify
Priority: 1025
Auth-Type: Primary
Auth:
requisite pam_faillock.so preauth
Account-Type: Primary
Account:
required pam_faillock.so
EOF
fi
DEBIAN_FRONTEND=noninteractive pam-auth-update
fi
AUTH_FILES=("/etc/pam.d/common-auth")
SKIP_FAILLOCK_CHECK=true
FAILLOCK_CONF="/etc/security/faillock.conf"
if [ -f $FAILLOCK_CONF ] || [ "$SKIP_FAILLOCK_CHECK" = "true" ]; then
regex="^\s*audit"
line="audit"
if ! grep -q $regex $FAILLOCK_CONF; then
echo $line >> $FAILLOCK_CONF
fi
else
for pam_file in "${AUTH_FILES[@]}"
do
if ! grep -qE '^\s*auth.*pam_faillock\.so (preauth|authfail).*audit' "$pam_file"; then
sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*preauth.*silent.*/ s/$/ audit/' "$pam_file"
fi
done
fi
|
Lock Accounts After Failed Password Attempts
[ref]ruleThis rule configures the system to lock out accounts after a number of incorrect login attempts
using pam_faillock.so .
pam_faillock.so module requires multiple entries in pam files. These entries must be carefully
defined to work as expected.
Ensure that the file /etc/security/faillock.conf contains the following entry:
deny = <count>
Where count should be less than or equal to
3 and greater than 0. Warning:
If the system relies on authselect tool to manage PAM settings, the remediation
will also use authselect tool. However, if any manual modification was made in
PAM files, the authselect integrity check will fail and the remediation will be
aborted in order to preserve intentional changes. In this case, an informative message will
be shown in the remediation report.
If the system supports the /etc/security/faillock.conf file, the pam_faillock
parameters should be defined in faillock.conf file. Rationale:By limiting the number of failed logon attempts, the risk of unauthorized system access via
user password guessing, also known as brute-forcing, is reduced. Limits are imposed by locking
the account. References:
R31, 5.4.2, 1, 12, 15, 16, 5.5.3, DSS05.04, DSS05.10, DSS06.10, 3.1.8, CCI-000044, CCI-002238, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9, 0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561, A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3, CM-6(a), AC-7(a), PR.AC-7, FIA_AFL.1, Req-8.1.6, 8.3.4, 8.3, SRG-OS-000329-GPOS-00128, SRG-OS-000021-GPOS-00005, UBTU-22-411045, SV-260549r958388_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
var_accounts_passwords_pam_faillock_deny='3'
if [ -f /usr/bin/authselect ]; then
if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
authselect enable-feature with-faillock
authselect apply-changes -b
else
conf_name=cac_faillock
if [ ! -f /usr/share/pam-configs/"$conf_name" ]; then
cat << EOF > /usr/share/pam-configs/"$conf_name"
Name: Enable pam_faillock to deny access
Default: yes
Conflicts: faillock
Priority: 0
Auth-Type: Primary
Auth:
[default=die] pam_faillock.so authfail
EOF
fi
if [ ! -f /usr/share/pam-configs/"$conf_name"_notify ]; then
cat << EOF > /usr/share/pam-configs/"$conf_name"_notify
Name: Notify of failed login attempts and reset count upon success
Default: yes
Conflicts: faillock_notify
Priority: 1025
Auth-Type: Primary
Auth:
requisite pam_faillock.so preauth
Account-Type: Primary
Account:
required pam_faillock.so
EOF
fi
DEBIAN_FRONTEND=noninteractive pam-auth-update
fi
AUTH_FILES=("/etc/pam.d/common-auth")
SKIP_FAILLOCK_CHECK=true
FAILLOCK_CONF="/etc/security/faillock.conf"
if [ -f $FAILLOCK_CONF ] || [ "$SKIP_FAILLOCK_CHECK" = "true" ]; then
regex="^\s*deny\s*="
line="deny = $var_accounts_passwords_pam_faillock_deny"
if ! grep -q $regex $FAILLOCK_CONF; then
echo $line >> $FAILLOCK_CONF
else
sed -i --follow-symlinks 's|^\s*\(deny\s*=\s*\)\(\S\+\)|\1'"$var_accounts_passwords_pam_faillock_deny"'|g' $FAILLOCK_CONF
fi
else
for pam_file in "${AUTH_FILES[@]}"
do
if ! grep -qE '^\s*auth.*pam_faillock\.so (preauth|authfail).*deny' "$pam_file"; then
sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*preauth.*silent.*/ s/$/ deny='"$var_accounts_passwords_pam_faillock_deny"'/' "$pam_file"
sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*authfail.*/ s/$/ deny='"$var_accounts_passwords_pam_faillock_deny"'/' "$pam_file"
else
sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*preauth.*silent.*\)\('"deny"'=\)[0-9]\+\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_deny"'\3/' "$pam_file"
sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*authfail.*\)\('"deny"'=\)[0-9]\+\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_deny"'\3/' "$pam_file"
fi
done
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Set Interval For Counting Failed Password Attempts
[ref]ruleUtilizing pam_faillock.so , the fail_interval directive configures the system
to lock out an account after a number of incorrect login attempts within a specified time
period.
Ensure that the file /etc/security/faillock.conf contains the following entry:
fail_interval = <interval-in-seconds> where interval-in-seconds is 900 or greater. Warning:
If the system relies on authselect tool to manage PAM settings, the remediation
will also use authselect tool. However, if any manual modification was made in
PAM files, the authselect integrity check will fail and the remediation will be
aborted in order to preserve intentional changes. In this case, an informative message will
be shown in the remediation report.
If the system supports the /etc/security/faillock.conf file, the pam_faillock
parameters should be defined in faillock.conf file. Rationale:By limiting the number of failed logon attempts the risk of unauthorized system
access via user password guessing, otherwise known as brute-forcing, is reduced.
Limits are imposed by locking the account. References:
R31, 5.4.2, 1, 12, 15, 16, DSS05.04, DSS05.10, DSS06.10, CCI-000044, CCI-002238, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9, 0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561, A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3, CM-6(a), AC-7(a), PR.AC-7, FIA_AFL.1, SRG-OS-000329-GPOS-00128, SRG-OS-000021-GPOS-00005, UBTU-22-411045, SV-260549r958388_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
var_accounts_passwords_pam_faillock_fail_interval='900'
if [ -f /usr/bin/authselect ]; then
if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
authselect enable-feature with-faillock
authselect apply-changes -b
else
conf_name=cac_faillock
if [ ! -f /usr/share/pam-configs/"$conf_name" ]; then
cat << EOF > /usr/share/pam-configs/"$conf_name"
Name: Enable pam_faillock to deny access
Default: yes
Conflicts: faillock
Priority: 0
Auth-Type: Primary
Auth:
[default=die] pam_faillock.so authfail
EOF
fi
if [ ! -f /usr/share/pam-configs/"$conf_name"_notify ]; then
cat << EOF > /usr/share/pam-configs/"$conf_name"_notify
Name: Notify of failed login attempts and reset count upon success
Default: yes
Conflicts: faillock_notify
Priority: 1025
Auth-Type: Primary
Auth:
requisite pam_faillock.so preauth
Account-Type: Primary
Account:
required pam_faillock.so
EOF
fi
DEBIAN_FRONTEND=noninteractive pam-auth-update
fi
AUTH_FILES=("/etc/pam.d/common-auth")
SKIP_FAILLOCK_CHECK=true
FAILLOCK_CONF="/etc/security/faillock.conf"
if [ -f $FAILLOCK_CONF ] || [ "$SKIP_FAILLOCK_CHECK" = "true" ]; then
regex="^\s*fail_interval\s*="
line="fail_interval = $var_accounts_passwords_pam_faillock_fail_interval"
if ! grep -q $regex $FAILLOCK_CONF; then
echo $line >> $FAILLOCK_CONF
else
sed -i --follow-symlinks 's|^\s*\(fail_interval\s*=\s*\)\(\S\+\)|\1'"$var_accounts_passwords_pam_faillock_fail_interval"'|g' $FAILLOCK_CONF
fi
else
for pam_file in "${AUTH_FILES[@]}"
do
if ! grep -qE '^\s*auth.*pam_faillock\.so (preauth|authfail).*fail_interval' "$pam_file"; then
sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*preauth.*silent.*/ s/$/ fail_interval='"$var_accounts_passwords_pam_faillock_fail_interval"'/' "$pam_file"
sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*authfail.*/ s/$/ fail_interval='"$var_accounts_passwords_pam_faillock_fail_interval"'/' "$pam_file"
else
sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*preauth.*silent.*\)\('"fail_interval"'=\)[0-9]\+\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_fail_interval"'\3/' "$pam_file"
sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*authfail.*\)\('"fail_interval"'=\)[0-9]\+\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_fail_interval"'\3/' "$pam_file"
fi
done
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Do Not Show System Messages When Unsuccessful Logon Attempts Occur
[ref]ruleThis rule ensures the system prevents informative messages from being presented to the user
pertaining to logon information after a number of incorrect login attempts using
pam_faillock.so .
pam_faillock.so module requires multiple entries in pam files. These entries must be carefully
defined to work as expected. In order to avoid errors when manually editing these files, it is
recommended to use the appropriate tools, such as authselect or authconfig ,
depending on the OS version. Warning:
If the system relies on authselect tool to manage PAM settings, the remediation
will also use authselect tool. However, if any manual modification was made in
PAM files, the authselect integrity check will fail and the remediation will be
aborted in order to preserve intentional changes. In this case, an informative message will
be shown in the remediation report.
If the system supports the /etc/security/faillock.conf file, the pam_faillock
parameters should be defined in faillock.conf file. Rationale:The pam_faillock module without the silent option will leak information about the existence or
non-existence of a user account in the system because the failures are not recorded for unknown
users. The message about the user account being locked is never displayed for non-existing user
accounts allowing the adversary to infer that a particular account exists or not on the system. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-411045
- accounts_passwords_pam_faillock_silent
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Do Not Show System Messages When Unsuccessful Logon Attempts Occur - Check
if system relies on authselect tool
ansible.builtin.stat:
path: /usr/bin/authselect
register: result_authselect_present
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-411045
- accounts_passwords_pam_faillock_silent
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Do Not Show System Messages When Unsuccessful Logon Attempts Occur - Remediation
where authselect tool is present
block:
- name: Do Not Show System Messages When Unsuccessful Logon Attempts Occur - Check
integrity of authselect current profile
ansible.builtin.command:
cmd: authselect check
register: result_authselect_check_cmd
changed_when: false
failed_when: false
- name: Do Not Show System Messages When Unsuccessful Logon Attempts Occur - Informative
message based on the authselect integrity check result
ansible.builtin.assert:
that:
- result_authselect_check_cmd.rc == 0
fail_msg:
- authselect integrity check failed. Remediation aborted!
- This remediation could not be applied because an authselect profile was not
selected or the selected profile is not intact.
- It is not recommended to manually edit the PAM files when authselect tool
is available.
- In cases where the default authselect profile does not cover a specific demand,
a custom authselect profile is recommended.
success_msg:
- authselect integrity check passed
- name: Do Not Show System Messages When Unsuccessful Logon Attempts Occur - Get
authselect current features
ansible.builtin.shell:
cmd: authselect current | tail -n+3 | awk '{ print $2 }'
register: result_authselect_features
changed_when: false
when:
- result_authselect_check_cmd is success
- name: Do Not Show System Messages When Unsuccessful Logon Attempts Occur - Ensure
"with-faillock" feature is enabled using authselect tool
ansible.builtin.command:
cmd: authselect enable-feature with-faillock
register: result_authselect_enable_feature_cmd
when:
- result_authselect_check_cmd is success
- result_authselect_features.stdout is not search("with-faillock")
- name: Do Not Show System Messages When Unsuccessful Logon Attempts Occur - Ensure
authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b
when:
- result_authselect_enable_feature_cmd is not skipped
- result_authselect_enable_feature_cmd is success
when:
- '"libpam-runtime" in ansible_facts.packages'
- result_authselect_present.stat.exists
tags:
- DISA-STIG-UBTU-22-411045
- accounts_passwords_pam_faillock_silent
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Do Not Show System Messages When Unsuccessful Logon Attempts Occur - Remediation
where authselect tool is not present
block:
- name: Do Not Show System Messages When Unsuccessful Logon Attempts Occur - Check
if pam_faillock.so is already enabled
ansible.builtin.lineinfile:
path: /etc/pam.d/system-auth
regexp: .*auth.*pam_faillock\.so (preauth|authfail)
state: absent
check_mode: true
changed_when: false
register: result_pam_faillock_is_enabled
- name: Do Not Show System Messages When Unsuccessful Logon Attempts Occur - Enable
pam_faillock.so preauth editing PAM files
ansible.builtin.lineinfile:
path: '{{ item }}'
line: auth required pam_faillock.so preauth
insertbefore: ^auth.*sufficient.*pam_unix\.so.*
state: present
loop:
- /etc/pam.d/system-auth
- /etc/pam.d/password-auth
when:
- result_pam_faillock_is_enabled.found == 0
- name: Do Not Show System Messages When Unsuccessful Logon Attempts Occur - Enable
pam_faillock.so authfail editing PAM files
ansible.builtin.lineinfile:
path: '{{ item }}'
line: auth required pam_faillock.so authfail
insertbefore: ^auth.*required.*pam_deny\.so.*
state: present
loop:
- /etc/pam.d/system-auth
- /etc/pam.d/password-auth
when:
- result_pam_faillock_is_enabled.found == 0
- name: Do Not Show System Messages When Unsuccessful Logon Attempts Occur - Enable
pam_faillock.so account section editing PAM files
ansible.builtin.lineinfile:
path: '{{ item }}'
line: account required pam_faillock.so
insertbefore: ^account.*required.*pam_unix\.so.*
state: present
loop:
- /etc/pam.d/system-auth
- /etc/pam.d/password-auth
when:
- result_pam_faillock_is_enabled.found == 0
when:
- '"libpam-runtime" in ansible_facts.packages'
- not result_authselect_present.stat.exists
tags:
- DISA-STIG-UBTU-22-411045
- accounts_passwords_pam_faillock_silent
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Do Not Show System Messages When Unsuccessful Logon Attempts Occur - Check
the presence of /etc/security/faillock.conf file
ansible.builtin.stat:
path: /etc/security/faillock.conf
register: result_faillock_conf_check
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-411045
- accounts_passwords_pam_faillock_silent
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Do Not Show System Messages When Unsuccessful Logon Attempts Occur - Ensure
the pam_faillock.so silent parameter in /etc/security/faillock.conf
ansible.builtin.lineinfile:
path: /etc/security/faillock.conf
regexp: ^\s*silent
line: silent
state: present
when:
- '"libpam-runtime" in ansible_facts.packages'
- result_faillock_conf_check.stat.exists
tags:
- DISA-STIG-UBTU-22-411045
- accounts_passwords_pam_faillock_silent
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Do Not Show System Messages When Unsuccessful Logon Attempts Occur - Ensure
the pam_faillock.so silent parameter in PAM files
block:
- name: Do Not Show System Messages When Unsuccessful Logon Attempts Occur - Ensure
the inclusion of pam_faillock.so preauth silent parameter in auth section
ansible.builtin.lineinfile:
path: '{{ item }}'
backrefs: true
regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so preauth(:?(?!silent).)*)
line: \1required\3 silent
state: present
loop:
- /etc/pam.d/system-auth
- /etc/pam.d/password-auth
when:
- '"libpam-runtime" in ansible_facts.packages'
- not result_faillock_conf_check.stat.exists
tags:
- DISA-STIG-UBTU-22-411045
- accounts_passwords_pam_faillock_silent
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
if [ -f /usr/bin/authselect ]; then
if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
authselect enable-feature with-faillock
authselect apply-changes -b
else
conf_name=cac_faillock
if [ ! -f /usr/share/pam-configs/"$conf_name" ]; then
cat << EOF > /usr/share/pam-configs/"$conf_name"
Name: Enable pam_faillock to deny access
Default: yes
Conflicts: faillock
Priority: 0
Auth-Type: Primary
Auth:
[default=die] pam_faillock.so authfail
EOF
fi
if [ ! -f /usr/share/pam-configs/"$conf_name"_notify ]; then
cat << EOF > /usr/share/pam-configs/"$conf_name"_notify
Name: Notify of failed login attempts and reset count upon success
Default: yes
Conflicts: faillock_notify
Priority: 1025
Auth-Type: Primary
Auth:
requisite pam_faillock.so preauth
Account-Type: Primary
Account:
required pam_faillock.so
EOF
fi
DEBIAN_FRONTEND=noninteractive pam-auth-update
fi
AUTH_FILES=("/etc/pam.d/common-auth")
SKIP_FAILLOCK_CHECK=true
FAILLOCK_CONF="/etc/security/faillock.conf"
if [ -f $FAILLOCK_CONF ] || [ "$SKIP_FAILLOCK_CHECK" = "true" ]; then
regex="^\s*silent"
line="silent"
if ! grep -q $regex $FAILLOCK_CONF; then
echo $line >> $FAILLOCK_CONF
fi
else
for pam_file in "${AUTH_FILES[@]}"
do
if ! grep -qE '^\s*auth.*pam_faillock\.so (preauth|authfail).*silent' "$pam_file"; then
sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*preauth.*silent.*/ s/$/ silent/' "$pam_file"
fi
done
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Set Lockout Time for Failed Password Attempts
[ref]ruleThis rule configures the system to lock out accounts during a specified time period after a
number of incorrect login attempts using pam_faillock.so .
Ensure that the file /etc/security/faillock.conf contains the following entry:
unlock_time=<interval-in-seconds> where
interval-in-seconds is 0 or greater.
pam_faillock.so module requires multiple entries in pam files. These entries must be carefully
defined to work as expected. In order to avoid any errors when manually editing these files,
it is recommended to use the appropriate tools, such as authselect or authconfig ,
depending on the OS version.
If unlock_time is set to 0 , manual intervention by an administrator is required
to unlock a user. This should be done using the faillock tool. Warning:
If the system supports the new /etc/security/faillock.conf file but the
pam_faillock.so parameters are defined directly in /etc/pam.d/system-auth and
/etc/pam.d/password-auth , the remediation will migrate the unlock_time parameter
to /etc/security/faillock.conf to ensure compatibility with authselect tool.
The parameters deny and fail_interval , if used, also have to be migrated
by their respective remediation. Warning:
If the system relies on authselect tool to manage PAM settings, the remediation
will also use authselect tool. However, if any manual modification was made in
PAM files, the authselect integrity check will fail and the remediation will be
aborted in order to preserve intentional changes. In this case, an informative message will
be shown in the remediation report.
If the system supports the /etc/security/faillock.conf file, the pam_faillock
parameters should be defined in faillock.conf file. Rationale:By limiting the number of failed logon attempts the risk of unauthorized system
access via user password guessing, otherwise known as brute-forcing, is reduced.
Limits are imposed by locking the account. References:
R31, 5.4.2, 1, 12, 15, 16, 5.5.3, DSS05.04, DSS05.10, DSS06.10, 3.1.8, CCI-000044, CCI-002238, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9, 0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561, A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3, CM-6(a), AC-7(b), PR.AC-7, FIA_AFL.1, Req-8.1.7, 8.3.4, 8.3, SRG-OS-000329-GPOS-00128, SRG-OS-000021-GPOS-00005, UBTU-22-411045, SV-260549r958388_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
var_accounts_passwords_pam_faillock_unlock_time='0'
if [ -f /usr/bin/authselect ]; then
if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
authselect enable-feature with-faillock
authselect apply-changes -b
else
conf_name=cac_faillock
if [ ! -f /usr/share/pam-configs/"$conf_name" ]; then
cat << EOF > /usr/share/pam-configs/"$conf_name"
Name: Enable pam_faillock to deny access
Default: yes
Conflicts: faillock
Priority: 0
Auth-Type: Primary
Auth:
[default=die] pam_faillock.so authfail
EOF
fi
if [ ! -f /usr/share/pam-configs/"$conf_name"_notify ]; then
cat << EOF > /usr/share/pam-configs/"$conf_name"_notify
Name: Notify of failed login attempts and reset count upon success
Default: yes
Conflicts: faillock_notify
Priority: 1025
Auth-Type: Primary
Auth:
requisite pam_faillock.so preauth
Account-Type: Primary
Account:
required pam_faillock.so
EOF
fi
DEBIAN_FRONTEND=noninteractive pam-auth-update
fi
AUTH_FILES=("/etc/pam.d/common-auth")
SKIP_FAILLOCK_CHECK=true
FAILLOCK_CONF="/etc/security/faillock.conf"
if [ -f $FAILLOCK_CONF ] || [ "$SKIP_FAILLOCK_CHECK" = "true" ]; then
regex="^\s*unlock_time\s*="
line="unlock_time = $var_accounts_passwords_pam_faillock_unlock_time"
if ! grep -q $regex $FAILLOCK_CONF; then
echo $line >> $FAILLOCK_CONF
else
sed -i --follow-symlinks 's|^\s*\(unlock_time\s*=\s*\)\(\S\+\)|\1'"$var_accounts_passwords_pam_faillock_unlock_time"'|g' $FAILLOCK_CONF
fi
else
for pam_file in "${AUTH_FILES[@]}"
do
if ! grep -qE '^\s*auth.*pam_faillock\.so (preauth|authfail).*unlock_time' "$pam_file"; then
sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*preauth.*silent.*/ s/$/ unlock_time='"$var_accounts_passwords_pam_faillock_unlock_time"'/' "$pam_file"
sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*authfail.*/ s/$/ unlock_time='"$var_accounts_passwords_pam_faillock_unlock_time"'/' "$pam_file"
else
sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*preauth.*silent.*\)\('"unlock_time"'=\)[0-9]\+\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_unlock_time"'\3/' "$pam_file"
sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*authfail.*\)\('"unlock_time"'=\)[0-9]\+\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_unlock_time"'\3/' "$pam_file"
fi
done
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Set Password Quality Requirements
[ref]groupThe default pam_pwquality PAM module provides strength
checking for passwords. It performs a number of checks, such as
making sure passwords are not similar to dictionary words, are of
at least a certain length, are not the previous password reversed,
and are not simply a change of case from the previous password. It
can also require passwords to be in certain character classes. The
pam_pwquality module is the preferred way of configuring
password requirements.
The man pages pam_pwquality(8)
provide information on the capabilities and configuration of
each. |
contains 9 rules |
Set Password Quality Requirements with pam_pwquality
[ref]groupThe pam_pwquality PAM module can be configured to meet
requirements for a variety of policies.
For example, to configure pam_pwquality to require at least one uppercase
character, lowercase character, digit, and other (special)
character, make sure that pam_pwquality exists in /etc/pam.d/system-auth :
password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
If no such line exists, add one as the first line of the password section in /etc/pam.d/system-auth .
Next, modify the settings in /etc/security/pwquality.conf to match the following:
difok = 4
minlen = 14
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
maxrepeat = 3
The arguments can be modified to ensure compliance with
your organization's security policy. Discussion of each parameter follows. |
contains 9 rules |
Ensure PAM Enforces Password Requirements - Minimum Digit Characters
[ref]ruleThe pam_pwquality module's dcredit parameter controls requirements for
usage of digits in a password. When set to a negative number, any password will be required to
contain that many digits. When set to a positive number, pam_pwquality will grant +1 additional
length credit for each digit. Modify the dcredit setting in
/etc/security/pwquality.conf to require the use of a digit in passwords. Rationale:Use of a complex password helps to increase the time and resources required
to compromise the password. Password complexity, or strength, is a measure of
the effectiveness of a password in resisting attempts at guessing and brute-force
attacks.
Password complexity is one factor of several that determines how long it takes
to crack a password. The more complex the password, the greater the number of
possible combinations that need to be tested before the password is compromised.
Requiring digits makes password guessing attacks more difficult by ensuring a larger
search space. References:
R31, 5.4.1, 1, 12, 15, 16, 5, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, CCI-004066, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, 0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561, A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3, IA-5(c), IA-5(1)(a), CM-6(a), IA-5(4), PR.AC-1, PR.AC-6, PR.AC-7, FMT_SMF_EXT.1, Req-8.2.3, 8.3.6, 8.3, SRG-OS-000071-GPOS-00039, UBTU-22-611020, SV-260562r986287_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-611020
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.6
- accounts_password_pam_dcredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_password_pam_dcredit # promote to variable
set_fact:
var_password_pam_dcredit: !!str -1
tags:
- always
- name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Check
if system relies on pam-auth-update tool
ansible.builtin.stat:
path: /usr/sbin/pam-auth-update
register: result_pam_auth_update_present
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-611020
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.6
- accounts_password_pam_dcredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Remediation
where pam-auth-update tool is present
block:
- name: Check if /usr/share/pam-configs/cac_pwquality exists
stat:
path: /usr/share/pam-configs/cac_pwquality
register: pwquality_file_stat
- name: Put the content into /usr/share/pam-configs/cac_pwquality if it does not
exist
copy:
dest: /usr/share/pam-configs/cac_pwquality
content: |
Name: Pwquality password strength checking
Default: yes
Priority: 1024
Conflicts: cracklib
Password-Type: Primary
Password:
requisite pam_pwquality.so retry=3
force: true
when: not pwquality_file_stat.stat.exists
- name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Ensure
pam-auth-update profile changes are applied
ansible.builtin.command:
cmd: pam-auth-update --enable cac_pwquality
when:
- '"libpam-runtime" in ansible_facts.packages'
- result_pam_auth_update_present.stat.exists
tags:
- DISA-STIG-UBTU-22-611020
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.6
- accounts_password_pam_dcredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Ensure
PAM variable dcredit is set accordingly
ansible.builtin.lineinfile:
create: true
dest: /etc/security/pwquality.conf
regexp: ^#?\s*dcredit
line: dcredit = {{ var_password_pam_dcredit }}
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-611020
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.6
- accounts_password_pam_dcredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
var_password_pam_dcredit='-1'
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^dcredit")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_dcredit"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^dcredit\\>" "/etc/security/pwquality.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^dcredit\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
fi
printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure PAM Enforces Password Requirements - Prevent the Use of Dictionary Words
[ref]ruleThe pam_pwquality module's dictcheck check if passwords contains dictionary words. When
dictcheck is set to 1 passwords will be checked for dictionary words. Rationale:Use of a complex password helps to increase the time and resources required to compromise the password.
Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at
guessing and brute-force attacks.
Password complexity is one factor of several that determines how long it takes to crack a password. The more
complex the password, the greater the number of possible combinations that need to be tested before the
password is compromised.
Passwords with dictionary words may be more vulnerable to password-guessing attacks. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-611030
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_dictcheck
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_password_pam_dictcheck # promote to variable
set_fact:
var_password_pam_dictcheck: !!str 1
tags:
- always
- name: Ensure PAM Enforces Password Requirements - Prevent the Use of Dictionary
Words - Check if system relies on pam-auth-update tool
ansible.builtin.stat:
path: /usr/sbin/pam-auth-update
register: result_pam_auth_update_present
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-611030
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_dictcheck
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Prevent the Use of Dictionary
Words - Remediation where pam-auth-update tool is present
block:
- name: Check if /usr/share/pam-configs/cac_pwquality exists
stat:
path: /usr/share/pam-configs/cac_pwquality
register: pwquality_file_stat
- name: Put the content into /usr/share/pam-configs/cac_pwquality if it does not
exist
copy:
dest: /usr/share/pam-configs/cac_pwquality
content: |
Name: Pwquality password strength checking
Default: yes
Priority: 1024
Conflicts: cracklib
Password-Type: Primary
Password:
requisite pam_pwquality.so retry=3
force: true
when: not pwquality_file_stat.stat.exists
- name: Ensure PAM Enforces Password Requirements - Prevent the Use of Dictionary
Words - Ensure pam-auth-update profile changes are applied
ansible.builtin.command:
cmd: pam-auth-update --enable cac_pwquality
when:
- '"libpam-runtime" in ansible_facts.packages'
- result_pam_auth_update_present.stat.exists
tags:
- DISA-STIG-UBTU-22-611030
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_dictcheck
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Prevent the Use of Dictionary
Words - Ensure PAM variable dictcheck is set accordingly
ansible.builtin.lineinfile:
create: true
dest: /etc/security/pwquality.conf
regexp: ^#?\s*dictcheck
line: dictcheck = {{ var_password_pam_dictcheck }}
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-611030
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_dictcheck
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
var_password_pam_dictcheck='1'
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^dictcheck")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_dictcheck"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^dictcheck\\>" "/etc/security/pwquality.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^dictcheck\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
fi
printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure PAM Enforces Password Requirements - Minimum Different Characters
[ref]ruleThe pam_pwquality module's difok parameter sets the number of characters
in a password that must not be present in and old password during a password change.
Modify the difok setting in /etc/security/pwquality.conf
to equal 8 to require differing characters
when changing passwords. Rationale:Use of a complex password helps to increase the time and resources
required to compromise the password. Password complexity, or strength,
is a measure of the effectiveness of a password in resisting attempts
at guessing and brute–force attacks.
Password complexity is one factor of several that determines how long
it takes to crack a password. The more complex the password, the
greater the number of possible combinations that need to be tested
before the password is compromised.
Requiring a minimum number of different characters during password changes ensures that
newly changed passwords should not resemble previously compromised ones.
Note that passwords which are changed on compromised systems will still be compromised, however. References:
1, 12, 15, 16, 5, 5.6.2.1.1, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, CCI-004066, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3, IA-5(c), IA-5(1)(b), CM-6(a), IA-5(4), PR.AC-1, PR.AC-6, PR.AC-7, SRG-OS-000072-GPOS-00040, UBTU-22-611040, SV-260566r986290_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.6.2.1.1
- DISA-STIG-UBTU-22-611040
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(b)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_difok
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_password_pam_difok # promote to variable
set_fact:
var_password_pam_difok: !!str 8
tags:
- always
- name: Ensure PAM Enforces Password Requirements - Minimum Different Characters -
Check if system relies on pam-auth-update tool
ansible.builtin.stat:
path: /usr/sbin/pam-auth-update
register: result_pam_auth_update_present
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- CJIS-5.6.2.1.1
- DISA-STIG-UBTU-22-611040
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(b)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_difok
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Minimum Different Characters -
Remediation where pam-auth-update tool is present
block:
- name: Check if /usr/share/pam-configs/cac_pwquality exists
stat:
path: /usr/share/pam-configs/cac_pwquality
register: pwquality_file_stat
- name: Put the content into /usr/share/pam-configs/cac_pwquality if it does not
exist
copy:
dest: /usr/share/pam-configs/cac_pwquality
content: |
Name: Pwquality password strength checking
Default: yes
Priority: 1024
Conflicts: cracklib
Password-Type: Primary
Password:
requisite pam_pwquality.so retry=3
force: true
when: not pwquality_file_stat.stat.exists
- name: Ensure PAM Enforces Password Requirements - Minimum Different Characters
- Ensure pam-auth-update profile changes are applied
ansible.builtin.command:
cmd: pam-auth-update --enable cac_pwquality
when:
- '"libpam-runtime" in ansible_facts.packages'
- result_pam_auth_update_present.stat.exists
tags:
- CJIS-5.6.2.1.1
- DISA-STIG-UBTU-22-611040
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(b)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_difok
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Minimum Different Characters -
Ensure PAM variable difok is set accordingly
ansible.builtin.lineinfile:
create: true
dest: /etc/security/pwquality.conf
regexp: ^#?\s*difok
line: difok = {{ var_password_pam_difok }}
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- CJIS-5.6.2.1.1
- DISA-STIG-UBTU-22-611040
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(b)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_difok
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
var_password_pam_difok='8'
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^difok")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_difok"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^difok\\>" "/etc/security/pwquality.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^difok\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
fi
printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure PAM Enforces Password Requirements - Enforcing
[ref]ruleVerify that the operating system uses "pwquality" to enforce the
password complexity rules.
Verify the pwquality module is being enforced by operating system by
running the following command:
$ grep -i enforcing /etc/security/pwquality.conf
enforcing = 1
If the value of "enforcing" is not "1" or the line is commented out,
this is a finding.Rationale:Use of a complex password helps to increase the time and resources
required to compromise the password. Password complexity, or strength,
is a measure of the effectiveness of a password in resisting attempts at
guessing and brute-force attacks. Using enforcing=1 ensures "pwquality"
enforces complex password construction configuration and has the ability
to limit brute-force attacks on the system. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-611045
- accounts_password_pam_enforcing
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Enforcing
lineinfile:
path: /etc/security/pwquality.conf
create: true
regexp: ''
line: enforcing = 1
state: present
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-611045
- accounts_password_pam_enforcing
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
if [ -e "/etc/security/pwquality.conf" ] ; then
LC_ALL=C sed -i "/^\s*enforcing = 1/Id" "/etc/security/pwquality.conf"
else
touch "/etc/security/pwquality.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/security/pwquality.conf"
cp "/etc/security/pwquality.conf" "/etc/security/pwquality.conf.bak"
# Insert at the end of the file
printf '%s\n' "enforcing = 1" >> "/etc/security/pwquality.conf"
# Clean up after ourselves.
rm "/etc/security/pwquality.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
[ref]ruleThe pam_pwquality module's lcredit parameter controls requirements for
usage of lowercase letters in a password. When set to a negative number, any password will be required to
contain that many lowercase characters. When set to a positive number, pam_pwquality will grant +1 additional
length credit for each lowercase character. Modify the lcredit setting in
/etc/security/pwquality.conf to require the use of a lowercase character in passwords. Rationale:Use of a complex password helps to increase the time and resources required
to compromise the password. Password complexity, or strength, is a measure of
the effectiveness of a password in resisting attempts at guessing and brute-force
attacks.
Password complexity is one factor of several that determines how long it takes
to crack a password. The more complex the password, the greater the number of
possble combinations that need to be tested before the password is compromised.
Requiring a minimum number of lowercase characters makes password guessing attacks
more difficult by ensuring a larger search space. References:
R31, 5.4.1, 1, 12, 15, 16, 5, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, CCI-004066, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, 0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561, A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3, IA-5(c), IA-5(1)(a), CM-6(a), IA-5(4), PR.AC-1, PR.AC-6, PR.AC-7, FMT_SMF_EXT.1, Req-8.2.3, 8.3.6, 8.3, SRG-OS-000070-GPOS-00038, UBTU-22-611015, SV-260561r986286_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-611015
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.6
- accounts_password_pam_lcredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_password_pam_lcredit # promote to variable
set_fact:
var_password_pam_lcredit: !!str -1
tags:
- always
- name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters -
Check if system relies on pam-auth-update tool
ansible.builtin.stat:
path: /usr/sbin/pam-auth-update
register: result_pam_auth_update_present
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-611015
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.6
- accounts_password_pam_lcredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters -
Remediation where pam-auth-update tool is present
block:
- name: Check if /usr/share/pam-configs/cac_pwquality exists
stat:
path: /usr/share/pam-configs/cac_pwquality
register: pwquality_file_stat
- name: Put the content into /usr/share/pam-configs/cac_pwquality if it does not
exist
copy:
dest: /usr/share/pam-configs/cac_pwquality
content: |
Name: Pwquality password strength checking
Default: yes
Priority: 1024
Conflicts: cracklib
Password-Type: Primary
Password:
requisite pam_pwquality.so retry=3
force: true
when: not pwquality_file_stat.stat.exists
- name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
- Ensure pam-auth-update profile changes are applied
ansible.builtin.command:
cmd: pam-auth-update --enable cac_pwquality
when:
- '"libpam-runtime" in ansible_facts.packages'
- result_pam_auth_update_present.stat.exists
tags:
- DISA-STIG-UBTU-22-611015
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.6
- accounts_password_pam_lcredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters -
Ensure PAM variable lcredit is set accordingly
ansible.builtin.lineinfile:
create: true
dest: /etc/security/pwquality.conf
regexp: ^#?\s*lcredit
line: lcredit = {{ var_password_pam_lcredit }}
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-611015
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.6
- accounts_password_pam_lcredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
var_password_pam_lcredit='-1'
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^lcredit")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_lcredit"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^lcredit\\>" "/etc/security/pwquality.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^lcredit\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
fi
printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure PAM Enforces Password Requirements - Minimum Length
[ref]ruleThe pam_pwquality module's minlen parameter controls requirements for
minimum characters required in a password. Add minlen=15
after pam_pwquality to set minimum password length requirements. Rationale:The shorter the password, the lower the number of possible combinations
that need to be tested before the password is compromised.
Password complexity, or strength, is a measure of the effectiveness of a
password in resisting attempts at guessing and brute-force attacks.
Password length is one factor of several that helps to determine strength
and how long it takes to crack a password. Use of more characters in a password
helps to exponentially increase the time and/or resources required to
compromise the password. References:
R31, R68, 5.4.1, 1, 12, 15, 16, 5, 5.6.2.1.1, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, CCI-004066, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, 0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561, A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3, IA-5(c), IA-5(1)(a), CM-6(a), IA-5(4), PR.AC-1, PR.AC-6, PR.AC-7, FMT_SMF_EXT.1, Req-8.2.3, 8.3.6, 8.3, SRG-OS-000078-GPOS-00046, UBTU-22-611035, SV-260565r986289_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.6.2.1.1
- DISA-STIG-UBTU-22-611035
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.6
- accounts_password_pam_minlen
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_password_pam_minlen # promote to variable
set_fact:
var_password_pam_minlen: !!str 15
tags:
- always
- name: Ensure PAM Enforces Password Requirements - Minimum Length - Check if system
relies on pam-auth-update tool
ansible.builtin.stat:
path: /usr/sbin/pam-auth-update
register: result_pam_auth_update_present
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- CJIS-5.6.2.1.1
- DISA-STIG-UBTU-22-611035
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.6
- accounts_password_pam_minlen
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Minimum Length - Remediation where
pam-auth-update tool is present
block:
- name: Check if /usr/share/pam-configs/cac_pwquality exists
stat:
path: /usr/share/pam-configs/cac_pwquality
register: pwquality_file_stat
- name: Put the content into /usr/share/pam-configs/cac_pwquality if it does not
exist
copy:
dest: /usr/share/pam-configs/cac_pwquality
content: |
Name: Pwquality password strength checking
Default: yes
Priority: 1024
Conflicts: cracklib
Password-Type: Primary
Password:
requisite pam_pwquality.so retry=3
force: true
when: not pwquality_file_stat.stat.exists
- name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure pam-auth-update
profile changes are applied
ansible.builtin.command:
cmd: pam-auth-update --enable cac_pwquality
when:
- '"libpam-runtime" in ansible_facts.packages'
- result_pam_auth_update_present.stat.exists
tags:
- CJIS-5.6.2.1.1
- DISA-STIG-UBTU-22-611035
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.6
- accounts_password_pam_minlen
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure PAM variable
minlen is set accordingly
ansible.builtin.lineinfile:
create: true
dest: /etc/security/pwquality.conf
regexp: ^#?\s*minlen
line: minlen = {{ var_password_pam_minlen }}
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- CJIS-5.6.2.1.1
- DISA-STIG-UBTU-22-611035
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.6
- accounts_password_pam_minlen
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
var_password_pam_minlen='15'
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^minlen")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_minlen"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^minlen\\>" "/etc/security/pwquality.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^minlen\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
fi
printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure PAM Enforces Password Requirements - Minimum Special Characters
[ref]ruleThe pam_pwquality module's ocredit= parameter controls requirements for
usage of special (or "other") characters in a password. When set to a negative number,
any password will be required to contain that many special characters.
When set to a positive number, pam_pwquality will grant +1
additional length credit for each special character. Modify the ocredit setting
in /etc/security/pwquality.conf to equal -1
to require use of a special character in passwords. Rationale:Use of a complex password helps to increase the time and resources required
to compromise the password. Password complexity, or strength, is a measure of
the effectiveness of a password in resisting attempts at guessing and brute-force
attacks.
Password complexity is one factor of several that determines how long it takes
to crack a password. The more complex the password, the greater the number of
possible combinations that need to be tested before the password is compromised.
Requiring a minimum number of special characters makes password guessing attacks
more difficult by ensuring a larger search space. References:
R31, 5.4.1, 1, 12, 15, 16, 5, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, CCI-004066, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, 0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561, A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3, IA-5(c), IA-5(1)(a), CM-6(a), IA-5(4), PR.AC-1, PR.AC-6, PR.AC-7, FMT_SMF_EXT.1, SRG-OS-000266-GPOS-00101, UBTU-22-611025, SV-260563r991561_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-611025
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_ocredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_password_pam_ocredit # promote to variable
set_fact:
var_password_pam_ocredit: !!str -1
tags:
- always
- name: Ensure PAM Enforces Password Requirements - Minimum Special Characters - Check
if system relies on pam-auth-update tool
ansible.builtin.stat:
path: /usr/sbin/pam-auth-update
register: result_pam_auth_update_present
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-611025
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_ocredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Minimum Special Characters - Remediation
where pam-auth-update tool is present
block:
- name: Check if /usr/share/pam-configs/cac_pwquality exists
stat:
path: /usr/share/pam-configs/cac_pwquality
register: pwquality_file_stat
- name: Put the content into /usr/share/pam-configs/cac_pwquality if it does not
exist
copy:
dest: /usr/share/pam-configs/cac_pwquality
content: |
Name: Pwquality password strength checking
Default: yes
Priority: 1024
Conflicts: cracklib
Password-Type: Primary
Password:
requisite pam_pwquality.so retry=3
force: true
when: not pwquality_file_stat.stat.exists
- name: Ensure PAM Enforces Password Requirements - Minimum Special Characters -
Ensure pam-auth-update profile changes are applied
ansible.builtin.command:
cmd: pam-auth-update --enable cac_pwquality
when:
- '"libpam-runtime" in ansible_facts.packages'
- result_pam_auth_update_present.stat.exists
tags:
- DISA-STIG-UBTU-22-611025
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_ocredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Minimum Special Characters - Ensure
PAM variable ocredit is set accordingly
ansible.builtin.lineinfile:
create: true
dest: /etc/security/pwquality.conf
regexp: ^#?\s*ocredit
line: ocredit = {{ var_password_pam_ocredit }}
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-611025
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- accounts_password_pam_ocredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
var_password_pam_ocredit='-1'
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^ocredit")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_ocredit"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^ocredit\\>" "/etc/security/pwquality.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^ocredit\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
fi
printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure PAM Enforces Password Requirements - Authentication Retry Prompts Permitted Per-Session
[ref]ruleTo configure the number of retry prompts that are permitted per-session:
Edit the pam_pwquality.so statement in
/etc/pam.d/common-password to show
retry=3 , or a lower value if site
policy is more restrictive. The DoD requirement is a maximum of 3 prompts
per session. Rationale:Setting the password retry prompts that are permitted on a per-session basis to a low value
requires some software, such as SSH, to re-connect. This can slow down and
draw additional attention to some types of password-guessing attacks. Note that this
is different from account lockout, which is provided by the pam_faillock module. References:
R68, 5.4.1, 1, 11, 12, 15, 16, 3, 5, 9, 5.5.3, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, CCI-004066, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 7.6, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3, CM-6(a), AC-7(a), IA-5(4), PR.AC-1, PR.AC-6, PR.AC-7, PR.IP-1, SRG-OS-000069-GPOS-00037, SRG-OS-000480-GPOS-00227, UBTU-22-611045, SV-260567r991587_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
var_password_pam_retry='3'
conf_name=cac_pwquality
if [ ! -f /usr/share/pam-configs/"$conf_name" ]; then
cat << EOF > /usr/share/pam-configs/"$conf_name"
Name: Pwquality password strength checking
Default: yes
Priority: 1025
Conflicts: cracklib, pwquality
Password-Type: Primary
Password:
requisite pam_pwquality.so
EOF
fi
DEBIAN_FRONTEND=noninteractive pam-auth-update
PWQUALITY_CONF="/etc/security/pwquality.conf"
regex="^\s*retry\s*="
line="retry = $var_password_pam_retry"
if ! grep -q $regex $PWQUALITY_CONF; then
echo $line >> $PWQUALITY_CONF
else
sed -i --follow-symlinks 's|^\s*\(retry\s*=\s*\)\(\S\+\)|\1'"$var_password_pam_retry"'|g' $PWQUALITY_CONF
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
[ref]ruleThe pam_pwquality module's ucredit= parameter controls requirements for
usage of uppercase letters in a password. When set to a negative number, any password will be required to
contain that many uppercase characters. When set to a positive number, pam_pwquality will grant +1 additional
length credit for each uppercase character. Modify the ucredit setting in
/etc/security/pwquality.conf to require the use of an uppercase character in passwords. Rationale:Use of a complex password helps to increase the time and resources required to compromise the password.
Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts
at guessing and brute-force attacks.
Password complexity is one factor of several that determines how long it takes to crack a password. The more
complex the password, the greater the number of possible combinations that need to be tested before
the password is compromised. References:
R31, 5.4.1, 1, 12, 15, 16, 5, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, CCI-004066, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, 0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561, A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3, IA-5(c), IA-5(1)(a), CM-6(a), IA-5(4), PR.AC-1, PR.AC-6, PR.AC-7, FMT_SMF_EXT.1, Req-8.2.3, SRG-OS-000069-GPOS-00037, SRG-OS-000070-GPOS-00038, UBTU-22-611010, SV-260560r986285_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-611010
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- accounts_password_pam_ucredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_password_pam_ucredit # promote to variable
set_fact:
var_password_pam_ucredit: !!str -1
tags:
- always
- name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters -
Check if system relies on pam-auth-update tool
ansible.builtin.stat:
path: /usr/sbin/pam-auth-update
register: result_pam_auth_update_present
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-611010
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- accounts_password_pam_ucredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters -
Remediation where pam-auth-update tool is present
block:
- name: Check if /usr/share/pam-configs/cac_pwquality exists
stat:
path: /usr/share/pam-configs/cac_pwquality
register: pwquality_file_stat
- name: Put the content into /usr/share/pam-configs/cac_pwquality if it does not
exist
copy:
dest: /usr/share/pam-configs/cac_pwquality
content: |
Name: Pwquality password strength checking
Default: yes
Priority: 1024
Conflicts: cracklib
Password-Type: Primary
Password:
requisite pam_pwquality.so retry=3
force: true
when: not pwquality_file_stat.stat.exists
- name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
- Ensure pam-auth-update profile changes are applied
ansible.builtin.command:
cmd: pam-auth-update --enable cac_pwquality
when:
- '"libpam-runtime" in ansible_facts.packages'
- result_pam_auth_update_present.stat.exists
tags:
- DISA-STIG-UBTU-22-611010
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- accounts_password_pam_ucredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters -
Ensure PAM variable ucredit is set accordingly
ansible.builtin.lineinfile:
create: true
dest: /etc/security/pwquality.conf
regexp: ^#?\s*ucredit
line: ucredit = {{ var_password_pam_ucredit }}
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-611010
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(4)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- accounts_password_pam_ucredit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
var_password_pam_ucredit='-1'
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^ucredit")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_ucredit"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^ucredit\\>" "/etc/security/pwquality.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^ucredit\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
fi
printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Set Password Hashing Algorithm
[ref]groupThe system's default algorithm for storing password hashes in
/etc/shadow is SHA-512. This can be configured in several
locations. |
contains 2 rules |
Set Password Hashing Algorithm in /etc/login.defs
[ref]ruleIn /etc/login.defs , add or update the following line to ensure the system will use
SHA512 as the hashing algorithm:
ENCRYPT_METHOD SHA512 Rationale:Passwords need to be protected at all times, and encryption is the standard method for
protecting passwords. If passwords are not encrypted, they can be plainly read
(i.e., clear text) and easily compromised. Passwords that are encrypted with a weak algorithm
are no more protected than if they are kept in plain text.
Using a stronger hashing algorithm makes password cracking attacks more difficult. References:
5.4.4, 1, 12, 15, 16, 5, 5.6.2.2, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, 3.13.11, CCI-004062, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, 0418, 1055, 1402, A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3, IA-5(c), IA-5(1)(c), CM-6(a), PR.AC-1, PR.AC-6, PR.AC-7, Req-8.2.1, 8.3.2, 8.3, SRG-OS-000073-GPOS-00041, UBTU-22-611070, SV-260572r971535_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'login' 2>/dev/null | grep -q '^installed'; then
var_password_hashing_algorithm='SHA512'
# Allow multiple algorithms, but choose the first one for remediation
#
var_password_hashing_algorithm="$(echo $var_password_hashing_algorithm | cut -d \| -f 1)"
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^ENCRYPT_METHOD")
# shellcheck disable=SC2059
printf -v formatted_output "%s %s" "$stripped_key" "$var_password_hashing_algorithm"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^ENCRYPT_METHOD\\>" "/etc/login.defs"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^ENCRYPT_METHOD\\>.*/$escaped_formatted_output/gi" "/etc/login.defs"
else
if [[ -s "/etc/login.defs" ]] && [[ -n "$(tail -c 1 -- "/etc/login.defs" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/login.defs"
fi
printf '%s\n' "$formatted_output" >> "/etc/login.defs"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Set PAM''s Password Hashing Algorithm
[ref]ruleThe PAM system service can be configured to only store encrypted representations of passwords.
In "/etc/pam.d/common-password", the password section of the file controls which
PAM modules to execute during a password change.
Set the pam_unix.so module in the password section to include the option
sha512 and no other hashing
algorithms as shown below:
password [success=1 default=ignore] pam_unix.so sha512 other arguments...
This will help ensure that new passwords for local users will be stored using the
sha512 algorithm.Warning:
The hashing algorithms to be used with pam_unix.so are defined with independent module
options. There are at least 7 possible algorithms and likely more algorithms will be
introduced along the time. Due the the number of options and its possible combinations,
the use of multiple hashing algorithm options may bring unexpected behaviors to the
system. For this reason the check will pass only when one hashing algorithm option is
defined and is aligned to the "var_password_hashing_algorithm_pam" variable. The
remediation will ensure the correct option and remove any other extra hashing algorithm
option. Rationale:Passwords need to be protected at all times, and encryption is the standard method for
protecting passwords. If passwords are not encrypted, they can be plainly read
(i.e., clear text) and easily compromised. Passwords that are encrypted with a weak algorithm
are no more protected than if they are kept in plain text.
This setting ensures user and group account administration utilities are configured to store
only encrypted representations of passwords. Additionally, the crypt_style
configuration option in /etc/libuser.conf ensures the use of a strong hashing
algorithm that makes password cracking attacks more difficult. References:
R68, 1, 12, 15, 16, 5, 5.6.2.2, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, 3.13.11, CCI-000196, CCI-000803, CCI-004062, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, 0418, 1055, 1402, A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3, IA-5(c), IA-5(1)(c), CM-6(a), PR.AC-1, PR.AC-6, PR.AC-7, Req-8.2.1, 8.3.2, 8.3, SRG-OS-000073-GPOS-00041, SRG-OS-000120-GPOS-00061, UBTU-22-611055, SV-260569r986291_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.6.2.2
- DISA-STIG-UBTU-22-611055
- NIST-800-171-3.13.11
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(c)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.1
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.2
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- set_password_hashing_algorithm_systemauth
- name: XCCDF Value var_password_hashing_algorithm_pam # promote to variable
set_fact:
var_password_hashing_algorithm_pam: !!str sha512
tags:
- always
- name: Set PAM's Password Hashing Algorithm - Check if /etc/pam.d/system-auth file
is present
ansible.builtin.stat:
path: /etc/pam.d/system-auth
register: result_pam_file_present
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- CJIS-5.6.2.2
- DISA-STIG-UBTU-22-611055
- NIST-800-171-3.13.11
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(c)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.1
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.2
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- set_password_hashing_algorithm_systemauth
- name: Set PAM's Password Hashing Algorithm - Check the proper remediation for the
system
block:
- name: Set PAM's Password Hashing Algorithm - Define the PAM file to be edited
as a local fact
ansible.builtin.set_fact:
pam_file_path: /etc/pam.d/system-auth
- name: Set PAM's Password Hashing Algorithm - Check if system relies on authselect
tool
ansible.builtin.stat:
path: /usr/bin/authselect
register: result_authselect_present
- name: Set PAM's Password Hashing Algorithm - Ensure authselect custom profile
is used if authselect is present
block:
- name: Set PAM's Password Hashing Algorithm - Check integrity of authselect current
profile
ansible.builtin.command:
cmd: authselect check
register: result_authselect_check_cmd
changed_when: false
failed_when: false
- name: Set PAM's Password Hashing Algorithm - Informative message based on the
authselect integrity check result
ansible.builtin.assert:
that:
- result_authselect_check_cmd.rc == 0
fail_msg:
- authselect integrity check failed. Remediation aborted!
- This remediation could not be applied because an authselect profile was
not selected or the selected profile is not intact.
- It is not recommended to manually edit the PAM files when authselect tool
is available.
- In cases where the default authselect profile does not cover a specific
demand, a custom authselect profile is recommended.
success_msg:
- authselect integrity check passed
- name: Set PAM's Password Hashing Algorithm - Get authselect current profile
ansible.builtin.shell:
cmd: authselect current -r | awk '{ print $1 }'
register: result_authselect_profile
changed_when: false
when:
- result_authselect_check_cmd is success
- name: Set PAM's Password Hashing Algorithm - Define the current authselect profile
as a local fact
ansible.builtin.set_fact:
authselect_current_profile: '{{ result_authselect_profile.stdout }}'
authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
when:
- result_authselect_profile is not skipped
- result_authselect_profile.stdout is match("custom/")
- name: Set PAM's Password Hashing Algorithm - Define the new authselect custom
profile as a local fact
ansible.builtin.set_fact:
authselect_current_profile: '{{ result_authselect_profile.stdout }}'
authselect_custom_profile: custom/hardening
when:
- result_authselect_profile is not skipped
- result_authselect_profile.stdout is not match("custom/")
- name: Set PAM's Password Hashing Algorithm - Get authselect current features
to also enable them in the custom profile
ansible.builtin.shell:
cmd: authselect current | tail -n+3 | awk '{ print $2 }'
register: result_authselect_features
changed_when: false
when:
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- name: Set PAM's Password Hashing Algorithm - Check if any custom profile with
the same name was already created
ansible.builtin.stat:
path: /etc/authselect/{{ authselect_custom_profile }}
register: result_authselect_custom_profile_present
changed_when: false
when:
- authselect_current_profile is not match("custom/")
- name: Set PAM's Password Hashing Algorithm - Create an authselect custom profile
based on the current profile
ansible.builtin.command:
cmd: authselect create-profile hardening -b {{ authselect_current_profile
}}
when:
- result_authselect_check_cmd is success
- authselect_current_profile is not match("^(custom/|local)")
- not result_authselect_custom_profile_present.stat.exists
- name: Set PAM's Password Hashing Algorithm - Create an authselect custom profile
based on sssd profile
ansible.builtin.command:
cmd: authselect create-profile hardening -b sssd
when:
- result_authselect_check_cmd is success
- authselect_current_profile is match("local")
- not result_authselect_custom_profile_present.stat.exists
- name: Set PAM's Password Hashing Algorithm - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- authselect_custom_profile is not match(authselect_current_profile)
- name: Set PAM's Password Hashing Algorithm - Ensure the authselect custom profile
is selected
ansible.builtin.command:
cmd: authselect select {{ authselect_custom_profile }}
register: result_pam_authselect_select_profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- authselect_custom_profile is not match(authselect_current_profile)
- name: Set PAM's Password Hashing Algorithm - Restore the authselect features
in the custom profile
ansible.builtin.command:
cmd: authselect enable-feature {{ item }}
loop: '{{ result_authselect_features.stdout_lines }}'
register: result_pam_authselect_restore_features
when:
- result_authselect_profile is not skipped
- result_authselect_features is not skipped
- result_pam_authselect_select_profile is not skipped
- name: Set PAM's Password Hashing Algorithm - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- result_pam_authselect_restore_features is not skipped
- name: Set PAM's Password Hashing Algorithm - Change the PAM file to be edited
according to the custom authselect profile
ansible.builtin.set_fact:
pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
| basename }}
when:
- result_authselect_present.stat.exists
- name: Set PAM's Password Hashing Algorithm - Define a fact for control already
filtered in case filters are used
ansible.builtin.set_fact:
pam_module_control: sufficient
- name: Set PAM's Password Hashing Algorithm - Check if expected PAM module line
is present in {{ pam_file_path }}
ansible.builtin.lineinfile:
path: '{{ pam_file_path }}'
regexp: ^\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_unix.so\s*.*
state: absent
check_mode: true
changed_when: false
register: result_pam_line_present
- name: Set PAM's Password Hashing Algorithm - Include or update the PAM module
line in {{ pam_file_path }}
block:
- name: Set PAM's Password Hashing Algorithm - Check if required PAM module line
is present in {{ pam_file_path }} with different control
ansible.builtin.lineinfile:
path: '{{ pam_file_path }}'
regexp: ^\s*password\s+.*\s+pam_unix.so\s*
state: absent
check_mode: true
changed_when: false
register: result_pam_line_other_control_present
- name: Set PAM's Password Hashing Algorithm - Ensure the correct control for
the required PAM module line in {{ pam_file_path }}
ansible.builtin.replace:
dest: '{{ pam_file_path }}'
regexp: ^(\s*password\s+).*(\bpam_unix.so.*)
replace: \1{{ pam_module_control }} \2
register: result_pam_module_edit
when:
- result_pam_line_other_control_present.found == 1
- name: Set PAM's Password Hashing Algorithm - Ensure the required PAM module
line is included in {{ pam_file_path }}
ansible.builtin.lineinfile:
dest: '{{ pam_file_path }}'
line: password {{ pam_module_control }} pam_unix.so
register: result_pam_module_add
when:
- result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
> 1
- name: Set PAM's Password Hashing Algorithm - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b
when:
- result_authselect_present is defined
- result_authselect_present.stat.exists
- |-
(result_pam_module_add is defined and result_pam_module_add.changed)
or (result_pam_module_edit is defined and result_pam_module_edit.changed)
when:
- result_pam_line_present.found is defined
- result_pam_line_present.found == 0
- name: Set PAM's Password Hashing Algorithm - Define a fact for control already
filtered in case filters are used
ansible.builtin.set_fact:
pam_module_control: sufficient
- name: Set PAM's Password Hashing Algorithm - Check if the required PAM module
option is present in {{ pam_file_path }}
ansible.builtin.lineinfile:
path: '{{ pam_file_path }}'
regexp: ^\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_unix.so\s*.*\s{{
var_password_hashing_algorithm_pam }}\b
state: absent
check_mode: true
changed_when: false
register: result_pam_module_set_password_hashing_algorithm_systemauth_option_present
- name: Set PAM's Password Hashing Algorithm - Ensure the "{{ var_password_hashing_algorithm_pam
}}" PAM option for "pam_unix.so" is included in {{ pam_file_path }}
ansible.builtin.lineinfile:
path: '{{ pam_file_path }}'
backrefs: true
regexp: ^(\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_unix.so.*)
line: \1 {{ var_password_hashing_algorithm_pam }}
state: present
register: result_pam_set_password_hashing_algorithm_systemauth_add
when:
- result_pam_module_set_password_hashing_algorithm_systemauth_option_present.found
== 0
- name: Set PAM's Password Hashing Algorithm - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b
when:
- result_authselect_present.stat.exists
- |-
(result_pam_set_password_hashing_algorithm_systemauth_add is defined and result_pam_set_password_hashing_algorithm_systemauth_add.changed)
or (result_pam_set_password_hashing_algorithm_systemauth_edit is defined and result_pam_set_password_hashing_algorithm_systemauth_edit.changed)
when:
- '"libpam-runtime" in ansible_facts.packages'
- result_pam_file_present.stat.exists
tags:
- CJIS-5.6.2.2
- DISA-STIG-UBTU-22-611055
- NIST-800-171-3.13.11
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(c)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.1
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.2
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- set_password_hashing_algorithm_systemauth
- name: Set PAM's Password Hashing Algorithm - Check if /etc/pam.d/system-auth File
is Present
ansible.builtin.stat:
path: /etc/pam.d/system-auth
register: result_pam_file_present
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- CJIS-5.6.2.2
- DISA-STIG-UBTU-22-611055
- NIST-800-171-3.13.11
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(c)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.1
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.2
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- set_password_hashing_algorithm_systemauth
- name: Set PAM's Password Hashing Algorithm - Check The Proper Remediation For The
System
block:
- name: Set PAM's Password Hashing Algorithm - Define the PAM file to be edited
as a local fact
ansible.builtin.set_fact:
pam_file_path: /etc/pam.d/system-auth
- name: Set PAM's Password Hashing Algorithm - Check if system relies on authselect
tool
ansible.builtin.stat:
path: /usr/bin/authselect
register: result_authselect_present
- name: Set PAM's Password Hashing Algorithm - Ensure authselect custom profile
is used if authselect is present
block:
- name: Set PAM's Password Hashing Algorithm - Check integrity of authselect current
profile
ansible.builtin.command:
cmd: authselect check
register: result_authselect_check_cmd
changed_when: false
failed_when: false
- name: Set PAM's Password Hashing Algorithm - Informative message based on the
authselect integrity check result
ansible.builtin.assert:
that:
- result_authselect_check_cmd.rc == 0
fail_msg:
- authselect integrity check failed. Remediation aborted!
- This remediation could not be applied because an authselect profile was
not selected or the selected profile is not intact.
- It is not recommended to manually edit the PAM files when authselect tool
is available.
- In cases where the default authselect profile does not cover a specific
demand, a custom authselect profile is recommended.
success_msg:
- authselect integrity check passed
- name: Set PAM's Password Hashing Algorithm - Get authselect current profile
ansible.builtin.shell:
cmd: authselect current -r | awk '{ print $1 }'
register: result_authselect_profile
changed_when: false
when:
- result_authselect_check_cmd is success
- name: Set PAM's Password Hashing Algorithm - Define the current authselect profile
as a local fact
ansible.builtin.set_fact:
authselect_current_profile: '{{ result_authselect_profile.stdout }}'
authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
when:
- result_authselect_profile is not skipped
- result_authselect_profile.stdout is match("custom/")
- name: Set PAM's Password Hashing Algorithm - Define the new authselect custom
profile as a local fact
ansible.builtin.set_fact:
authselect_current_profile: '{{ result_authselect_profile.stdout }}'
authselect_custom_profile: custom/hardening
when:
- result_authselect_profile is not skipped
- result_authselect_profile.stdout is not match("custom/")
- name: Set PAM's Password Hashing Algorithm - Get authselect current features
to also enable them in the custom profile
ansible.builtin.shell:
cmd: authselect current | tail -n+3 | awk '{ print $2 }'
register: result_authselect_features
changed_when: false
when:
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- name: Set PAM's Password Hashing Algorithm - Check if any custom profile with
the same name was already created
ansible.builtin.stat:
path: /etc/authselect/{{ authselect_custom_profile }}
register: result_authselect_custom_profile_present
changed_when: false
when:
- authselect_current_profile is not match("custom/")
- name: Set PAM's Password Hashing Algorithm - Create an authselect custom profile
based on the current profile
ansible.builtin.command:
cmd: authselect create-profile hardening -b {{ authselect_current_profile
}}
when:
- result_authselect_check_cmd is success
- authselect_current_profile is not match("^(custom/|local)")
- not result_authselect_custom_profile_present.stat.exists
- name: Set PAM's Password Hashing Algorithm - Create an authselect custom profile
based on sssd profile
ansible.builtin.command:
cmd: authselect create-profile hardening -b sssd
when:
- result_authselect_check_cmd is success
- authselect_current_profile is match("local")
- not result_authselect_custom_profile_present.stat.exists
- name: Set PAM's Password Hashing Algorithm - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- authselect_custom_profile is not match(authselect_current_profile)
- name: Set PAM's Password Hashing Algorithm - Ensure the authselect custom profile
is selected
ansible.builtin.command:
cmd: authselect select {{ authselect_custom_profile }}
register: result_pam_authselect_select_profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- authselect_custom_profile is not match(authselect_current_profile)
- name: Set PAM's Password Hashing Algorithm - Restore the authselect features
in the custom profile
ansible.builtin.command:
cmd: authselect enable-feature {{ item }}
loop: '{{ result_authselect_features.stdout_lines }}'
register: result_pam_authselect_restore_features
when:
- result_authselect_profile is not skipped
- result_authselect_features is not skipped
- result_pam_authselect_select_profile is not skipped
- name: Set PAM's Password Hashing Algorithm - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- result_pam_authselect_restore_features is not skipped
- name: Set PAM's Password Hashing Algorithm - Change the PAM file to be edited
according to the custom authselect profile
ansible.builtin.set_fact:
pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
| basename }}
when:
- result_authselect_present.stat.exists
- name: Set PAM's Password Hashing Algorithm - Ensure That Only the Correct Hashing
Algorithm Option For pam_unix.so Is Used in /etc/pam.d/system-auth
ansible.builtin.replace:
dest: '{{ pam_file_path }}'
regexp: (^\s*password.*pam_unix\.so.*)\b{{ item }}\b\s*(.*)
replace: \1\2
when: item != var_password_hashing_algorithm_pam
loop:
- sha512
- yescrypt
- gost_yescrypt
- blowfish
- sha256
- md5
- bigcrypt
register: result_pam_hashing_options_removal
- name: Set PAM's Password Hashing Algorithm - Ensure authselect changes are applied
ansible.builtin.command:
cmd: authselect apply-changes -b
when:
- result_authselect_present.stat.exists
- result_pam_hashing_options_removal is changed
when:
- '"libpam-runtime" in ansible_facts.packages'
- result_pam_file_present.stat.exists
tags:
- CJIS-5.6.2.2
- DISA-STIG-UBTU-22-611055
- NIST-800-171-3.13.11
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(c)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.1
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.2
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- set_password_hashing_algorithm_systemauth
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
var_password_hashing_algorithm_pam='sha512'
conf_name=cac_unix
conf_path="/usr/share/pam-configs"
if [ ! -f "$conf_path"/"$conf_name" ]; then
if [ -f "$conf_path"/unix ]; then
if grep -q "$(md5sum "$conf_path"/unix | cut -d ' ' -f 1)" /var/lib/dpkg/info/libpam-runtime.md5sums;then
cp "$conf_path"/unix "$conf_path"/"$conf_name"
sed -i 's/Priority: [0-9]\+/Priority: 257\
Conflicts: unix/' "$conf_path"/"$conf_name"
DEBIAN_FRONTEND=noninteractive pam-auth-update
else
echo "Not applicable - checksum of $conf_path/unix does not match the original." >&2
fi
else
echo "Not applicable - $conf_path/unix does not exist" >&2
fi
fi
PAM_FILE_PATH=/usr/share/pam-configs/cac_unix
if ! grep -qzP "Password:\s*\n\s+.*\s+pam_unix.so\s+.*\b$var_password_hashing_algorithm_pam\b" "$PAM_FILE_PATH"; then
sed -i -E '/^Password:/,/^[^[:space:]]/ {
/pam_unix\.so/ {
s/$/ '"$var_password_hashing_algorithm_pam"'/g
}
}' "$PAM_FILE_PATH"
fi
if ! grep -qzP "Password-Initial:\s*\n\s+.*\s+pam_unix.so\s+.*\b$var_password_hashing_algorithm_pam\b" "$PAM_FILE_PATH"; then
sed -i -E '/^Password-Initial:/,/^[^[:space:]]/ {
/pam_unix\.so/ {
s/$/ '"$var_password_hashing_algorithm_pam"'/g
}
}' "$PAM_FILE_PATH"
fi
# Ensure only the correct hashing algorithm option is used.
declare -a HASHING_ALGORITHMS_OPTIONS=("sha512" "yescrypt" "gost_yescrypt" "blowfish" "sha256" "md5" "bigcrypt")
for hash_option in "${HASHING_ALGORITHMS_OPTIONS[@]}"; do
if [ "$hash_option" != "$var_password_hashing_algorithm_pam" ]; then
sed -i -E '/^Password:/,/^[^[:space:]]/ {
/pam_unix\.so/ {
s/\s*'"$hash_option"'//g
}
}' "$PAM_FILE_PATH"
sed -i -E '/^Password-Initial:/,/^[^[:space:]]/ {
/pam_unix\.so/ {
s/\s*'"$hash_option"'//g
}
}' "$PAM_FILE_PATH"
DEBIAN_FRONTEND=noninteractive pam-auth-update
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Install pam_pwquality Package
[ref]rule
The libpam-pwquality package can be installed with the following command:
$ apt-get install libpam-pwquality Rationale:Use of a complex password helps to increase the time and resources required
to compromise the password. Password complexity, or strength, is a measure
of the effectiveness of a password in resisting attempts at guessing and
brute-force attacks. "pwquality" enforces complex password construction
configuration and has the ability to limit brute-force attacks on the system. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-215010
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_pam_pwquality_installed
- name: Ensure libpam-pwquality is installed
package:
name: libpam-pwquality
state: present
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-215010
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_pam_pwquality_installed
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_libpam-pwquality
class install_libpam-pwquality {
package { 'libpam-pwquality':
ensure => 'installed',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "libpam-pwquality"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[[packages]]
name = "libpam-pwquality"
version = "*"
|
Ensure PAM Displays Last Logon/Access Notification
[ref]ruleTo configure the system to notify users of last logon/access using pam_lastlog ,
add or correct the pam_lastlog settings in /etc/pam.d/login
to include showfailed option, such as:
session required pam_lastlog.so showfailed
And make sure that the silent option is not set for this specific line.Warning:
If the system relies on authselect tool to manage PAM settings, the remediation
will also use authselect tool. However, if any manual modification was made in
PAM files, the authselect integrity check will fail and the remediation will be
aborted in order to preserve intentional changes. In this case, an informative message will
be shown in the remediation report. Warning:
authselect contains an authselect feature to easily and properly enable Last Logon
notifications with pam_lastlog.so module. If a custom profile was created and used
in the system before this authselect feature was available, the new feature can't be used
with this custom profile and the remediation will fail. In this case, the custom profile
should be recreated or manually updated. Rationale:Users need to be aware of activity that occurs regarding their account. Providing users with
information regarding the number of unsuccessful attempts that were made to login to their
account allows the user to determine if any unauthorized activity has occurred and gives them
an opportunity to notify administrators. References:
1, 12, 15, 16, 5.5.2, DSS05.04, DSS05.10, DSS06.10, CCI-000366, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9, 0582, 0584, 05885, 0586, 0846, 0957, A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3, AC-9, AC-9(1), PR.AC-7, Req-10.2.4, 10.2.1.4, 10.2.1, 10.2, SRG-OS-000480-GPOS-00227, UBTU-22-412015, SV-260551r991589_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
if [ -f /usr/bin/authselect ]; then
if authselect list-features sssd | grep -q with-silent-lastlog; then
if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
authselect disable-feature with-silent-lastlog
authselect apply-changes -b
else
if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
# If not already in use, a custom profile is created preserving the enabled features.
if [[ ! $CURRENT_PROFILE == custom/* ]]; then
ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
# The "local" profile does not contain essential security features required by multiple Benchmarks.
# If currently used, it is replaced by "sssd", which is the best option in this case.
if [[ $CURRENT_PROFILE == local ]]; then
CURRENT_PROFILE="sssd"
fi
authselect create-profile hardening -b $CURRENT_PROFILE
CURRENT_PROFILE="custom/hardening"
authselect apply-changes -b --backup=before-hardening-custom-profile
authselect select $CURRENT_PROFILE
for feature in $ENABLED_FEATURES; do
authselect enable-feature $feature;
done
authselect apply-changes -b --backup=after-hardening-custom-profile
fi
PAM_FILE_NAME=$(basename "/etc/pam.d/login")
PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"
authselect apply-changes -b
if [ -e "$PAM_FILE_PATH" ] ; then
PAM_FILE_PATH="$PAM_FILE_PATH"
if [ -f /usr/bin/authselect ]; then
if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
# If not already in use, a custom profile is created preserving the enabled features.
if [[ ! $CURRENT_PROFILE == custom/* ]]; then
ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
# The "local" profile does not contain essential security features required by multiple Benchmarks.
# If currently used, it is replaced by "sssd", which is the best option in this case.
if [[ $CURRENT_PROFILE == local ]]; then
CURRENT_PROFILE="sssd"
fi
authselect create-profile hardening -b $CURRENT_PROFILE
CURRENT_PROFILE="custom/hardening"
authselect apply-changes -b --backup=before-hardening-custom-profile
authselect select $CURRENT_PROFILE
for feature in $ENABLED_FEATURES; do
authselect enable-feature $feature;
done
authselect apply-changes -b --backup=after-hardening-custom-profile
fi
PAM_FILE_NAME=$(basename "$PAM_FILE_PATH")
PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"
authselect apply-changes -b
fi
if ! grep -qP "^\s*session\s+required\s+pam_lastlog.so\s*.*" "$PAM_FILE_PATH"; then
# Line matching group + control + module was not found. Check group + module.
if [ "$(grep -cP '^\s*session\s+.*\s+pam_lastlog.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
# The control is updated only if one single line matches.
sed -i -E --follow-symlinks "s/^(\s*session\s+).*(\bpam_lastlog.so.*)/\1required \2/" "$PAM_FILE_PATH"
else
LAST_MATCH_LINE=$(grep -nP "^\s*session.*include\s+common-session$" "$PAM_FILE_PATH" | tail -n 1 | cut -d: -f 1)
if [ ! -z $LAST_MATCH_LINE ]; then
sed -i --follow-symlinks $LAST_MATCH_LINE" a session required pam_lastlog.so" "$PAM_FILE_PATH"
else
echo "session required pam_lastlog.so" >> "$PAM_FILE_PATH"
fi
fi
fi
# Check the option
if ! grep -qP "^\s*session\s+required\s+pam_lastlog.so\s*.*\sshowfailed\b" "$PAM_FILE_PATH"; then
sed -i -E --follow-symlinks "/\s*session\s+required\s+pam_lastlog.so.*/ s/$/ showfailed/" "$PAM_FILE_PATH"
fi
if [ -f /usr/bin/authselect ]; then
authselect apply-changes -b
fi
else
echo "$PAM_FILE_PATH was not found" >&2
fi
if [ -e "$PAM_FILE_PATH" ] ; then
PAM_FILE_PATH="$PAM_FILE_PATH"
if [ -f /usr/bin/authselect ]; then
if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
# If not already in use, a custom profile is created preserving the enabled features.
if [[ ! $CURRENT_PROFILE == custom/* ]]; then
ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
# The "local" profile does not contain essential security features required by multiple Benchmarks.
# If currently used, it is replaced by "sssd", which is the best option in this case.
if [[ $CURRENT_PROFILE == local ]]; then
CURRENT_PROFILE="sssd"
fi
authselect create-profile hardening -b $CURRENT_PROFILE
CURRENT_PROFILE="custom/hardening"
authselect apply-changes -b --backup=before-hardening-custom-profile
authselect select $CURRENT_PROFILE
for feature in $ENABLED_FEATURES; do
authselect enable-feature $feature;
done
authselect apply-changes -b --backup=after-hardening-custom-profile
fi
PAM_FILE_NAME=$(basename "$PAM_FILE_PATH")
PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"
authselect apply-changes -b
fi
if grep -qP "^\s*session\s+required\s+pam_lastlog.so\s.*\bsilent\b" "$PAM_FILE_PATH"; then
sed -i -E --follow-symlinks "s/(.*session.*required.*pam_lastlog.so.*)\ssilent=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
fi
if [ -f /usr/bin/authselect ]; then
authselect apply-changes -b
fi
else
echo "$PAM_FILE_PATH was not found" >&2
fi
fi
else
if [ -e "/etc/pam.d/login" ] ; then
PAM_FILE_PATH="/etc/pam.d/login"
if [ -f /usr/bin/authselect ]; then
if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
# If not already in use, a custom profile is created preserving the enabled features.
if [[ ! $CURRENT_PROFILE == custom/* ]]; then
ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
# The "local" profile does not contain essential security features required by multiple Benchmarks.
# If currently used, it is replaced by "sssd", which is the best option in this case.
if [[ $CURRENT_PROFILE == local ]]; then
CURRENT_PROFILE="sssd"
fi
authselect create-profile hardening -b $CURRENT_PROFILE
CURRENT_PROFILE="custom/hardening"
authselect apply-changes -b --backup=before-hardening-custom-profile
authselect select $CURRENT_PROFILE
for feature in $ENABLED_FEATURES; do
authselect enable-feature $feature;
done
authselect apply-changes -b --backup=after-hardening-custom-profile
fi
PAM_FILE_NAME=$(basename "/etc/pam.d/login")
PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"
authselect apply-changes -b
fi
if ! grep -qP "^\s*session\s+required\s+pam_lastlog.so\s*.*" "$PAM_FILE_PATH"; then
# Line matching group + control + module was not found. Check group + module.
if [ "$(grep -cP '^\s*session\s+.*\s+pam_lastlog.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
# The control is updated only if one single line matches.
sed -i -E --follow-symlinks "s/^(\s*session\s+).*(\bpam_lastlog.so.*)/\1required \2/" "$PAM_FILE_PATH"
else
LAST_MATCH_LINE=$(grep -nP "^\s*session.*include\s+common-session$" "$PAM_FILE_PATH" | tail -n 1 | cut -d: -f 1)
if [ ! -z $LAST_MATCH_LINE ]; then
sed -i --follow-symlinks $LAST_MATCH_LINE" a session required pam_lastlog.so" "$PAM_FILE_PATH"
else
echo "session required pam_lastlog.so" >> "$PAM_FILE_PATH"
fi
fi
fi
# Check the option
if ! grep -qP "^\s*session\s+required\s+pam_lastlog.so\s*.*\sshowfailed\b" "$PAM_FILE_PATH"; then
sed -i -E --follow-symlinks "/\s*session\s+required\s+pam_lastlog.so.*/ s/$/ showfailed/" "$PAM_FILE_PATH"
fi
if [ -f /usr/bin/authselect ]; then
authselect apply-changes -b
fi
else
echo "/etc/pam.d/login was not found" >&2
fi
if [ -e "/etc/pam.d/login" ] ; then
PAM_FILE_PATH="/etc/pam.d/login"
if [ -f /usr/bin/authselect ]; then
if ! authselect check; then
echo "
authselect integrity check failed. Remediation aborted!
This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
It is not recommended to manually edit the PAM files when authselect tool is available.
In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
exit 1
fi
CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
# If not already in use, a custom profile is created preserving the enabled features.
if [[ ! $CURRENT_PROFILE == custom/* ]]; then
ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
# The "local" profile does not contain essential security features required by multiple Benchmarks.
# If currently used, it is replaced by "sssd", which is the best option in this case.
if [[ $CURRENT_PROFILE == local ]]; then
CURRENT_PROFILE="sssd"
fi
authselect create-profile hardening -b $CURRENT_PROFILE
CURRENT_PROFILE="custom/hardening"
authselect apply-changes -b --backup=before-hardening-custom-profile
authselect select $CURRENT_PROFILE
for feature in $ENABLED_FEATURES; do
authselect enable-feature $feature;
done
authselect apply-changes -b --backup=after-hardening-custom-profile
fi
PAM_FILE_NAME=$(basename "/etc/pam.d/login")
PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"
authselect apply-changes -b
fi
if grep -qP "^\s*session\s+required\s+pam_lastlog.so\s.*\bsilent\b" "$PAM_FILE_PATH"; then
sed -i -E --follow-symlinks "s/(.*session.*required.*pam_lastlog.so.*)\ssilent=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
fi
if [ -f /usr/bin/authselect ]; then
authselect apply-changes -b
fi
else
echo "/etc/pam.d/login was not found" >&2
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Protect Physical Console Access
[ref]groupIt is impossible to fully protect a system from an
attacker with physical access, so securing the space in which the
system is located should be considered a necessary step. However,
there are some steps which, if taken, make it more difficult for an
attacker to quickly or undetectably modify a system from its
console. |
contains 9 rules |
Configure Screen Locking
[ref]groupWhen a user must temporarily leave an account
logged-in, screen locking should be employed to prevent passersby
from abusing the account. User education and training is
particularly important for screen locking to be effective, and policies
can be implemented to reinforce this.
Automatic screen locking is only meant as a safeguard for
those cases where a user forgot to lock the screen. |
contains 8 rules |
Configure Console Screen Locking
[ref]groupA console screen locking mechanism is a temporary action taken when a user
stops work and moves away from the immediate physical vicinity of the
information system but does not logout because of the temporary nature of
the absence. Rather than relying on the user to manually lock their
operation system session prior to vacating the vicinity, operating systems
need to be able to identify when a user's session has idled and take action
to initiate the session lock. |
contains 1 rule |
Check that vlock is installed to allow session locking
[ref]ruleThe Ubuntu 22.04 operating system must have vlock installed to allow for session locking.
The vlock package can be installed with the following command:
$ apt-get install vlock Rationale:A session lock is a temporary action taken when a user stops work and
moves away from the immediate physical vicinity of the information
system but does not want to log out because of the temporary nature of
the absence.
The session lock is implemented at the point where session activity can
be determined.
Regardless of where the session lock is determined and implemented,
once invoked, the session lock must remain in place until the user
reauthenticates. No other activity aside from reauthentication must
unlock the system. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-412025
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- vlock_installed
- name: Ensure vlock is installed
package:
name: vlock
state: present
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-412025
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- vlock_installed
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_vlock
class install_vlock {
package { 'vlock':
ensure => 'installed',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "vlock"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[[packages]]
name = "vlock"
version = "*"
|
Hardware Tokens for Authentication
[ref]groupThe use of hardware tokens such as smart cards for system login
provides stronger, two-factor authentication than using a username and password.
In Red Hat Enterprise Linux servers and workstations, hardware token login
is not enabled by default and must be enabled in the system settings. |
contains 7 rules |
Install the opensc Package For Multifactor Authentication
[ref]rule
The opensc-pkcs11 package can be installed with the following command:
$ apt-get install opensc-pkcs11 Rationale:Using an authentication device, such as a CAC or token that is separate from
the information system, ensures that even if the information system is
compromised, that compromise will not affect credentials stored on the
authentication device.
Multifactor solutions that require devices separate from
information systems gaining access include, for example, hardware tokens
providing time-based or challenge-response authenticators and smart cards such
as the U.S. Government Personal Identity Verification card and the DoD Common
Access Card. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-612015
- NIST-800-53-CM-6(a)
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_opensc_installed
- name: Ensure opensc-pkcs11 is installed
package:
name: opensc-pkcs11
state: present
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-612015
- NIST-800-53-CM-6(a)
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_opensc_installed
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_opensc-pkcs11
class install_opensc-pkcs11 {
package { 'opensc-pkcs11':
ensure => 'installed',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "opensc-pkcs11"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[[packages]]
name = "opensc-pkcs11"
version = "*"
|
Install Smart Card Packages For Multifactor Authentication
[ref]ruleConfigure the operating system to implement multifactor authentication by
installing the required package with the following command:
The libpam-pkcs11 package can be installed with the following command:
$ apt-get install libpam-pkcs11 Rationale:Using an authentication device, such as a CAC or token that is separate from
the information system, ensures that even if the information system is
compromised, that compromise will not affect credentials stored on the
authentication device.
Multifactor solutions that require devices separate from
information systems gaining access include, for example, hardware tokens
providing time-based or challenge-response authenticators and smart cards such
as the U.S. Government Personal Identity Verification card and the DoD Common
Access Card. References:
CCI-000765, CCI-001953, CCI-001954, CCI-004046, CM-6(a), Req-8.3, SRG-OS-000105-GPOS-00052, SRG-OS-000375-GPOS-00160, SRG-OS-000375-GPOS-00161, SRG-OS-000377-GPOS-00162, UBTU-22-612010, SV-260573r986292_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-612010
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-8.3
- enable_strategy
- install_smartcard_packages
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure libpam-pkcs11 is installed
package:
name: libpam-pkcs11
state: present
when:
- '"linux-base" in ansible_facts.packages'
- ansible_architecture != "s390x"
tags:
- DISA-STIG-UBTU-22-612010
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-8.3
- enable_strategy
- install_smartcard_packages
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_libpam-pkcs11
class install_libpam-pkcs11 {
package { 'libpam-pkcs11':
ensure => 'installed',
}
}
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && { ! ( grep -sqE "^.*\.s390x$" /proc/sys/kernel/osrelease || grep -sqE "^s390x$" /proc/sys/kernel/arch; ); }; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "libpam-pkcs11"
if [ ! -f /etc/pam_pkcs11/pam_pkcs11.conf ]; then
cp /usr/share/doc/libpam-pkcs11/examples/pam_pkcs11.conf.example /etc/pam_pkcs11/pam_pkcs11.conf
fi
sed -i -e 's/debug = true/debug = false/g' -e 's|module = /usr/lib/opensc-pkcs11|module = /usr/lib/x86_64-linux-gnu/pkcs11/opensc-pkcs11|' /etc/pam_pkcs11/pam_pkcs11.conf
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[[packages]]
name = "libpam-pkcs11"
version = "*"
|
Configure Smart Card Certificate Authority Validation
[ref]ruleConfigure the operating system to do certificate status checking for PKI
authentication. Modify all of the cert_policy lines in
/etc/pam_pkcs11/pam_pkcs11.conf to include ca like so:
cert_policy = ca, ocsp_on, signature; Rationale:Using an authentication device, such as a CAC or token that is separate from
the information system, ensures that even if the information system is
compromised, that compromise will not affect credentials stored on the
authentication device.
Multifactor solutions that require devices separate from
information systems gaining access include, for example, hardware tokens
providing time-based or challenge-response authenticators and smart cards such
as the U.S. Government Personal Identity Verification card and the DoD Common
Access Card. Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
if [ ! -f /etc/pam_pkcs11/pam_pkcs11.conf ]; then
cp /usr/share/doc/libpam-pkcs11/examples/pam_pkcs11.conf.example /etc/pam_pkcs11/pam_pkcs11.conf
fi
if grep -v "^\s*\#+cert_policy" /etc/pam_pkcs11/pam_pkcs11.conf | grep -qv "ca"; then
sed -i "s/\(^[[:blank:]]*\)\(\(\#*[[:blank:]]*cert_policy[[:blank:]]*=[[:blank:]]*.*;\)[^ $]*\)/\1cert_policy = ca,signature,ocsp_on;/" /etc/pam_pkcs11/pam_pkcs11.conf
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure Smart Card Certificate Status Checking
[ref]ruleConfigure the operating system to do certificate status checking for PKI
authentication. Modify all of the cert_policy lines in
/etc/pam_pkcs11/pam_pkcs11.conf to include ocsp_on like so:
cert_policy = ca, ocsp_on, signature; Rationale:Using an authentication device, such as a CAC or token that is separate from
the information system, ensures that even if the information system is
compromised, that compromise will not affect credentials stored on the
authentication device.
Multifactor solutions that require devices separate from
information systems gaining access include, for example, hardware tokens
providing time-based or challenge-response authenticators and smart cards such
as the U.S. Government Personal Identity Verification card and the DoD Common
Access Card. Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && { ! ( grep -sqE "^.*\.s390x$" /proc/sys/kernel/osrelease || grep -sqE "^s390x$" /proc/sys/kernel/arch; ); }; then
if [ ! -f /etc/pam_pkcs11/pam_pkcs11.conf ]; then
cp /usr/share/doc/libpam-pkcs11/examples/pam_pkcs11.conf.example /etc/pam_pkcs11/pam_pkcs11.conf
fi
if grep -v "^\s*\#+cert_policy" /etc/pam_pkcs11/pam_pkcs11.conf | grep -qv "oscp_on"; then
sed -i "s/\(^[[:blank:]]*\)\(\(\#*[[:blank:]]*cert_policy[[:blank:]]*=[[:blank:]]*.*;\)[^ $]*\)/\1cert_policy = ca,signature,ocsp_on;/" /etc/pam_pkcs11/pam_pkcs11.conf
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure Smart Card Local Cache of Revocation Data
[ref]ruleConfigure the operating system for PKI-based authentication to use
local revocation data when unable to access the network to obtain it
remotely. Modify all of the cert_policy lines in
/etc/pam_pkcs11/pam_pkcs11.conf to include crl_auto
or crl_offline like so:
cert_policy = ca,signature,ocsp_on,crl_auto; Rationale:Without configuring a local cache of revocation data, there is the
potential to allow access to users who are no longer authorized
(users with revoked certificates). Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
if [ ! -f /etc/pam_pkcs11/pam_pkcs11.conf ]; then
cp /usr/share/doc/libpam-pkcs11/examples/pam_pkcs11.conf.example /etc/pam_pkcs11/pam_pkcs11.conf
fi
if grep -v "^\s*\#+cert_policy" /etc/pam_pkcs11/pam_pkcs11.conf | grep -Eqv 'crl_auto|crl_offline'; then
sed -i "s/\(^[[:blank:]]*\)\(\(\#*[[:blank:]]*cert_policy[[:blank:]]*=[[:blank:]]*.*;\)[^ $]*\)/\1cert_policy = ca,signature,ocsp_on,crl_auto;/" /etc/pam_pkcs11/pam_pkcs11.conf
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Enable Smart Card Logins in PAM
[ref]ruleThis requirement only applies to components where this is specific to the
function of the device or has the concept of an organizational user (e.g.,
VPN, proxy capability). This does not apply to authentication for the
purpose of configuring the device itself (management).
Add or update the following line in /etc/pam.d/common-auth ,
placing it above any lines containing pam_unix.so :
auth [success=2 default=ignore] pam_pkcs11.so
For general information about enabling smart card authentication, consult
the documentation at:
Rationale:Smart card login provides two-factor authentication stronger than
that provided by a username and password combination. Smart cards leverage PKI
(public key infrastructure) in order to provide and verify credentials.
Using an authentication device, such as a CAC or token that is separate
from the information system, ensures that even if the information system is
compromised, that compromise will not affect credentials stored on the
authentication device.
Multifactor solutions that require devices separate from information
systems gaining access include, for example, hardware tokens providing
time-based or challenge-response authenticators and smart cards such as the
U.S. Government Personal Identity Verification card and the DoD Common
Access Card. References:
CCI-000765, CCI-000766, CCI-000767, CCI-000768, CCI-000187, CCI-004046, CCI-001953, CCI-001954, CCI-004047, SRG-OS-000068-GPOS-00036, SRG-OS-000105-GPOS-00052, SRG-OS-000106-GPOS-00053, SRG-OS-000107-GPOS-00054, SRG-OS-000108-GPOS-00055, SRG-OS-000375-GPOS-00160, SRG-OS-000375-GPOS-00161, SRG-OS-000375-GPOS-00162, UBTU-22-612020, SV-260575r986293_rule |
Verify that 'use_mappers' is set to 'pwent' in PAM
[ref]ruleThe operating system must map the authenticated identity to the user or
group account for PKI-based authentication.
Verify that use_mappers is set to pwent in
/etc/pam_pkcs11/pam_pkcs11.conf file with the following command:
$ grep ^use_mappers /etc/pam_pkcs11/pam_pkcs11.conf
use_mappers = pwent Rationale:Without mapping the certificate used to authenticate to the user account,
the ability to determine the identity of the individual user or group will
not be available for forensic analysis. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-612040
- low_complexity
- low_disruption
- low_severity
- no_reboot_needed
- restrict_strategy
- verify_use_mappers
- name: Verify that 'use_mappers' is set to 'pwent' in PAM
lineinfile:
path: /etc/pam_pkcs11/pam_pkcs11.conf
create: true
regexp: ''
line: use_mappers = pwent
state: present
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-612040
- low_complexity
- low_disruption
- low_severity
- no_reboot_needed
- restrict_strategy
- verify_use_mappers
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
if [ -e "/etc/pam_pkcs11/pam_pkcs11.conf" ] ; then
LC_ALL=C sed -i "/^\s*use_mappers = pwent/Id" "/etc/pam_pkcs11/pam_pkcs11.conf"
else
touch "/etc/pam_pkcs11/pam_pkcs11.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/pam_pkcs11/pam_pkcs11.conf"
cp "/etc/pam_pkcs11/pam_pkcs11.conf" "/etc/pam_pkcs11/pam_pkcs11.conf.bak"
# Insert at the end of the file
printf '%s\n' "use_mappers = pwent" >> "/etc/pam_pkcs11/pam_pkcs11.conf"
# Clean up after ourselves.
rm "/etc/pam_pkcs11/pam_pkcs11.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Disable Ctrl-Alt-Del Reboot Activation
[ref]ruleBy default, SystemD will reboot the system if the Ctrl-Alt-Del
key sequence is pressed.
To configure the system to ignore the Ctrl-Alt-Del key sequence from the
command line instead of rebooting the system, do either of the following:
ln -sf /dev/null /etc/systemd/system/ctrl-alt-del.target
or
systemctl mask ctrl-alt-del.target
Do not simply delete the /usr/lib/systemd/system/ctrl-alt-del.service file,
as this file may be restored during future system updates.Rationale:A locally logged-in user who presses Ctrl-Alt-Del, when at the console,
can reboot the system. If accidentally pressed, as could happen in
the case of mixed OS environment, this can create the risk of short-term
loss of availability of systems due to unintentional reboot. References:
12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, 3.4.5, CCI-000366, CCI-002235, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), 4.3.3.7.3, SR 2.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, CM-6(a), AC-6(1), PR.AC-4, PR.DS-5, FAU_GEN.1.2, SRG-OS-000324-GPOS-00125, SRG-OS-000480-GPOS-00227, UBTU-22-211015, SV-260469r991589_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-211015
- NIST-800-171-3.4.5
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-6(a)
- disable_ctrlaltdel_reboot
- disable_strategy
- high_severity
- low_complexity
- low_disruption
- no_reboot_needed
- name: Disable Ctrl-Alt-Del Reboot Activation
systemd:
name: ctrl-alt-del.target
force: true
masked: true
state: stopped
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-211015
- NIST-800-171-3.4.5
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-6(a)
- disable_ctrlaltdel_reboot
- disable_strategy
- high_severity
- low_complexity
- low_disruption
- no_reboot_needed
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
if [[ "$OSCAP_BOOTC_BUILD" == "YES" ]] ; then
systemctl disable ctrl-alt-del.target
systemctl mask ctrl-alt-del.target
else
systemctl disable --now ctrl-alt-del.target
systemctl mask --now ctrl-alt-del.target
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Protect Accounts by Restricting Password-Based Login
[ref]groupConventionally, Unix shell accounts are accessed by
providing a username and password to a login program, which tests
these values for correctness using the /etc/passwd and
/etc/shadow files. Password-based login is vulnerable to
guessing of weak passwords, and to sniffing and man-in-the-middle
attacks against passwords entered over a network or at an insecure
console. Therefore, mechanisms for accessing accounts by entering
usernames and passwords should be restricted to those which are
operationally necessary. |
contains 9 rules |
Set Account Expiration Parameters
[ref]groupAccounts can be configured to be automatically disabled
after a certain time period,
meaning that they will require administrator interaction to become usable again.
Expiration of accounts after inactivity can be set for all accounts by default
and also on a per-account basis, such as for accounts that are known to be temporary.
To configure automatic expiration of an account following
the expiration of its password (that is, after the password has expired and not been changed),
run the following command, substituting NUM_DAYS and USER appropriately:
$ sudo chage -I NUM_DAYS USER
Accounts, such as temporary accounts, can also be configured to expire on an explicitly-set date with the
-E option.
The file /etc/default/useradd controls
default settings for all newly-created accounts created with the system's
normal command line utilities.Warning:
This will only apply to newly created accounts |
contains 2 rules |
Set Account Expiration Following Inactivity
[ref]ruleTo specify the number of days after a password expires (which
signifies inactivity) until an account is permanently disabled, add or correct
the following line in /etc/default/useradd :
INACTIVE=35
If a password is currently on the verge of expiration, then
35
day(s) remain(s) until the account is automatically
disabled. However, if the password will not expire for another 60 days, then 60
days plus 35 day(s) could
elapse until the account would be automatically disabled. See the
useradd man page for more information.Rationale:Inactive identifiers pose a risk to systems and applications because attackers may exploit an inactive identifier and potentially obtain undetected access to the system.
Disabling inactive accounts ensures that accounts which may not have been responsibly removed are not available to attackers who may have compromised their credentials.
Owners of inactive accounts will not notice if unauthorized access to their user account has been obtained. References:
5.5.1.4, 1, 12, 13, 14, 15, 16, 18, 3, 5, 7, 8, 5.6.2.1.1, DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, 3.5.6, CCI-003628, CCI-003627, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 6.2, A.12.4.1, A.12.4.3, A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, IA-4(e), AC-2(3), CM-6(a), DE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, Req-8.1.4, 8.2.6, 8.2, SRG-OS-000118-GPOS-00060, UBTU-22-411035, SV-260547r986282_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'login' 2>/dev/null | grep -q '^installed'; then
var_account_disable_post_pw_expiration='35'
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^INACTIVE")
# shellcheck disable=SC2059
printf -v formatted_output "%s=%s" "$stripped_key" "$var_account_disable_post_pw_expiration"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^INACTIVE\\>" "/etc/default/useradd"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^INACTIVE\\>.*/$escaped_formatted_output/gi" "/etc/default/useradd"
else
if [[ -s "/etc/default/useradd" ]] && [[ -n "$(tail -c 1 -- "/etc/default/useradd" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/default/useradd"
fi
printf '%s\n' "$formatted_output" >> "/etc/default/useradd"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Assign Expiration Date to Temporary Accounts
[ref]ruleTemporary accounts are established as part of normal account activation
procedures when there is a need for short-term accounts. In the event
temporary accounts are required, configure the system to
terminate them after a documented time period. For every temporary account, run the following command to set an expiration date on
it, substituting USER and YYYY-MM-DD
appropriately:
$ sudo chage -E YYYY-MM-DD USER
YYYY-MM-DD indicates the documented expiration date for the
account. For U.S. Government systems, the operating system must be
configured to automatically terminate these types of accounts after a
period of 72 hours.Rationale:If temporary user accounts remain active when no longer needed or for
an excessive period, these accounts may be used to gain unauthorized access.
To mitigate this risk, automated termination of all temporary accounts
must be set upon account creation.
References:
1, 12, 13, 14, 15, 16, 18, 3, 5, 7, 8, DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS06.03, CCI-000016, CCI-001682, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, SR 1.1, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 6.2, A.12.4.1, A.12.4.3, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, AC-2(2), AC-2(3), CM-6(a), DE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6, SRG-OS-000123-GPOS-00064, SRG-OS-000002-GPOS-00002, UBTU-22-411040, SV-260548r958364_rule |
Set Password Expiration Parameters
[ref]groupThe file /etc/login.defs controls several
password-related settings. Programs such as passwd ,
su , and
login consult /etc/login.defs to determine
behavior with regard to password aging, expiration warnings,
and length. See the man page login.defs(5) for more information.
Users should be forced to change their passwords, in order to
decrease the utility of compromised passwords. However, the need to
change passwords often should be balanced against the risk that
users will reuse or write down passwords if forced to change them
too often. Forcing password changes every 90-360 days, depending on
the environment, is recommended. Set the appropriate value as
PASS_MAX_DAYS and apply it to existing accounts with the
-M flag.
The PASS_MIN_DAYS (-m ) setting prevents password
changes for 7 days after the first change, to discourage password
cycling. If you use this setting, train users to contact an administrator
for an emergency password change in case a new password becomes
compromised. The PASS_WARN_AGE (-W ) setting gives
users 7 days of warnings at login time that their passwords are about to expire.
For example, for each existing human user USER, expiration parameters
could be adjusted to a 180 day maximum password age, 7 day minimum password
age, and 7 day warning period with the following command:
$ sudo chage -M 180 -m 7 -W 7 USER |
contains 2 rules |
Set Password Maximum Age
[ref]ruleTo specify password maximum age for new accounts,
edit the file /etc/login.defs
and add or correct the following line:
PASS_MAX_DAYS 60
A value of 180 days is sufficient for many environments.
The DoD requirement is 60.
The profile requirement is 60 .Rationale:Any password, no matter how complex, can eventually be cracked. Therefore, passwords
need to be changed periodically. If the operating system does not limit the lifetime
of passwords and force users to change their passwords, there is the risk that the
operating system passwords could be compromised.
Setting the password maximum age ensures users are required to
periodically change their passwords. Requiring shorter password lifetimes
increases the risk of users writing down the password in a convenient
location subject to physical compromise. References:
5.5.1.2, 1, 12, 15, 16, 5, 5.6.2.1, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, 3.5.6, CCI-004066, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, 0418, 1055, 1402, A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3, IA-5(f), IA-5(1)(d), CM-6(a), PR.AC-1, PR.AC-6, PR.AC-7, Req-8.2.4, 8.3.9, 8.3, SRG-OS-000076-GPOS-00044, UBTU-22-411030, SV-260546r986281_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'login' 2>/dev/null | grep -q '^installed'; then
var_accounts_maximum_age_login_defs='60'
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^PASS_MAX_DAYS")
# shellcheck disable=SC2059
printf -v formatted_output "%s %s" "$stripped_key" "$var_accounts_maximum_age_login_defs"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^PASS_MAX_DAYS\\>" "/etc/login.defs"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^PASS_MAX_DAYS\\>.*/$escaped_formatted_output/gi" "/etc/login.defs"
else
if [[ -s "/etc/login.defs" ]] && [[ -n "$(tail -c 1 -- "/etc/login.defs" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/login.defs"
fi
printf '%s\n' "$formatted_output" >> "/etc/login.defs"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Set Password Minimum Age
[ref]ruleTo specify password minimum age for new accounts,
edit the file /etc/login.defs
and add or correct the following line:
PASS_MIN_DAYS 1
A value of 1 day is considered sufficient for many
environments. The DoD requirement is 1.
The profile requirement is 1 .Rationale:Enforcing a minimum password lifetime helps to prevent repeated password
changes to defeat the password reuse or history enforcement requirement. If
users are allowed to immediately and continually change their password,
then the password could be repeatedly changed in a short period of time to
defeat the organization's policy regarding password reuse.
Setting the minimum password age protects against users cycling back to a
favorite password after satisfying the password reuse requirement. References:
5.5.1.1, 1, 12, 15, 16, 5, 5.6.2.1.1, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, 3.5.8, CCI-004066, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, 0418, 1055, 1402, A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3, IA-5(f), IA-5(1)(d), CM-6(a), PR.AC-1, PR.AC-6, PR.AC-7, SRG-OS-000075-GPOS-00043, UBTU-22-411025, SV-260545r986280_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'login' 2>/dev/null | grep -q '^installed'; then
var_accounts_minimum_age_login_defs='1'
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^PASS_MIN_DAYS")
# shellcheck disable=SC2059
printf -v formatted_output "%s %s" "$stripped_key" "$var_accounts_minimum_age_login_defs"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^PASS_MIN_DAYS\\>" "/etc/login.defs"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^PASS_MIN_DAYS\\>.*/$escaped_formatted_output/gi" "/etc/login.defs"
else
if [[ -s "/etc/login.defs" ]] && [[ -n "$(tail -c 1 -- "/etc/login.defs" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/login.defs"
fi
printf '%s\n' "$formatted_output" >> "/etc/login.defs"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Verify Proper Storage and Existence of Password
Hashes
[ref]groupBy default, password hashes for local accounts are stored
in the second field (colon-separated) in
/etc/shadow . This file should be readable only by
processes running with root credentials, preventing users from
casually accessing others' password hashes and attempting
to crack them.
However, it remains possible to misconfigure the system
and store password hashes
in world-readable files such as /etc/passwd , or
to even store passwords themselves in plaintext on the system.
Using system-provided tools for password change/creation
should allow administrators to avoid such misconfiguration. |
contains 4 rules |
Ensure sudo group has only necessary members
[ref]ruleDevelopers and implementers can increase the assurance in security
functions by employing well-defined security policy models; structured,
disciplined, and rigorous hardware and software development techniques;
and sound system/security engineering principles. Implementation may
include isolation of memory space and libraries.
The Ubuntu operating system restricts access to security functions
through the use of access control mechanisms and by implementing least
privilege capabilities. Warning:
Due to the risk of removing user rights, automated remediation is
not available for this configuration check. Rationale:Any users assigned to the sudo group would be granted administrator
access to the system. |
Ensure no duplicate UIDs exist
[ref]ruleAlthough the useradd program will not let you create a duplicate User ID (UID),
it is possible for an administrator to manually edit the /etc/passwd file
and change the UID field. Users must be assigned unique UIDs for
accountability and to ensure appropriate access protections. Warning:
Due to the risk of removing user accounts or changing user's UIDS,
automated remediation is not available for this configuration check. Rationale:Users must be assigned unique UIDs for accountability and to ensure
appropriate access protections. |
Prevent Login to Accounts With Empty Password
[ref]ruleIf an account is configured for password authentication
but does not have an assigned password, it may be possible to log
into the account without authentication. Remove any instances of the
nullok in
/etc/pam.d/common-password
to prevent logins with empty passwords. Warning:
If the system relies on authselect tool to manage PAM settings, the remediation
will also use authselect tool. However, if any manual modification was made in
PAM files, the authselect integrity check will fail and the remediation will be
aborted in order to preserve intentional changes. In this case, an informative message will
be shown in the remediation report.
Note that this rule is not applicable for systems running within a
container. Having user with empty password within a container is not
considered a risk, because it should not be possible to directly login into
a container anyway. Rationale:If an account has an empty password, anyone could log in and
run commands with the privileges of that account. Accounts with
empty passwords should never be used in operational environments. References:
1, 12, 13, 14, 15, 16, 18, 3, 5, 5.5.2, APO01.06, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.02, DSS06.03, DSS06.10, 3.1.1, 3.1.5, CCI-000366, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.18.1.4, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, IA-5(1)(a), IA-5(c), CM-6(a), PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.DS-5, FIA_UAU.1, Req-8.2.3, 8.3.1, 8.3, SRG-OS-000480-GPOS-00227, UBTU-22-611060, SV-260570r991589_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.5.2
- DISA-STIG-UBTU-22-611060
- NIST-800-171-3.1.1
- NIST-800-171-3.1.5
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.1
- configure_strategy
- high_severity
- low_complexity
- medium_disruption
- no_empty_passwords
- no_reboot_needed
- name: Prevent Login to Accounts With Empty Password - Check if system relies on
authselect
ansible.builtin.stat:
path: /usr/bin/authselect
register: result_authselect_present
when: '"libpam-runtime" in ansible_facts.packages'
tags:
- CJIS-5.5.2
- DISA-STIG-UBTU-22-611060
- NIST-800-171-3.1.1
- NIST-800-171-3.1.5
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.1
- configure_strategy
- high_severity
- low_complexity
- medium_disruption
- no_empty_passwords
- no_reboot_needed
- name: Prevent Login to Accounts With Empty Password - Remediate using authselect
block:
- name: Prevent Login to Accounts With Empty Password - Check integrity of authselect
current profile
ansible.builtin.command:
cmd: authselect check
register: result_authselect_check_cmd
changed_when: false
failed_when: false
- name: Prevent Login to Accounts With Empty Password - Informative message based
on the authselect integrity check result
ansible.builtin.assert:
that:
- result_authselect_check_cmd.rc == 0
fail_msg:
- authselect integrity check failed. Remediation aborted!
- This remediation could not be applied because an authselect profile was not
selected or the selected profile is not intact.
- It is not recommended to manually edit the PAM files when authselect tool
is available.
- In cases where the default authselect profile does not cover a specific demand,
a custom authselect profile is recommended.
success_msg:
- authselect integrity check passed
- name: Prevent Login to Accounts With Empty Password - Get authselect current features
ansible.builtin.shell:
cmd: authselect current | tail -n+3 | awk '{ print $2 }'
register: result_authselect_features
changed_when: false
when:
- result_authselect_check_cmd is success
- name: Prevent Login to Accounts With Empty Password - Ensure "without-nullok"
feature is enabled using authselect tool
ansible.builtin.command:
cmd: authselect enable-feature without-nullok
register: result_authselect_enable_feature_cmd
when:
- result_authselect_check_cmd is success
- result_authselect_features.stdout is not search("without-nullok")
- name: Prevent Login to Accounts With Empty Password - Ensure authselect changes
are applied
ansible.builtin.command:
cmd: authselect apply-changes -b
when:
- result_authselect_enable_feature_cmd is not skipped
- result_authselect_enable_feature_cmd is success
when:
- '"libpam-runtime" in ansible_facts.packages'
- result_authselect_present.stat.exists
tags:
- CJIS-5.5.2
- DISA-STIG-UBTU-22-611060
- NIST-800-171-3.1.1
- NIST-800-171-3.1.5
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.1
- configure_strategy
- high_severity
- low_complexity
- medium_disruption
- no_empty_passwords
- no_reboot_needed
- name: Prevent Login to Accounts With Empty Password - Remediate directly editing
PAM files
ansible.builtin.replace:
dest: '{{ item }}'
regexp: nullok
loop:
- /etc/pam.d/common-password
when:
- '"libpam-runtime" in ansible_facts.packages'
- not result_authselect_present.stat.exists
tags:
- CJIS-5.5.2
- DISA-STIG-UBTU-22-611060
- NIST-800-171-3.1.1
- NIST-800-171-3.1.5
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(1)(a)
- NIST-800-53-IA-5(c)
- PCI-DSS-Req-8.2.3
- PCI-DSSv4-8.3
- PCI-DSSv4-8.3.1
- configure_strategy
- high_severity
- low_complexity
- medium_disruption
- no_empty_passwords
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
conf_name=cac_unix
conf_path="/usr/share/pam-configs"
if [ ! -f "$conf_path"/"$conf_name" ]; then
if [ -f "$conf_path"/unix ]; then
if grep -q "$(md5sum "$conf_path"/unix | cut -d ' ' -f 1)" /var/lib/dpkg/info/libpam-runtime.md5sums;then
cp "$conf_path"/unix "$conf_path"/"$conf_name"
sed -i 's/Priority: [0-9]\+/Priority: 257\
Conflicts: unix/' "$conf_path"/"$conf_name"
DEBIAN_FRONTEND=noninteractive pam-auth-update
else
echo "Not applicable - checksum of $conf_path/unix does not match the original." >&2
fi
else
echo "Not applicable - $conf_path/unix does not exist" >&2
fi
fi
config_file="/usr/share/pam-configs/cac_unix"
sed -i -E '/^Password:/,/^[^[:space:]]/ {
/pam_unix\.so/ {
s/\s*nullok//g
}
}' "$config_file"
sed -i -E '/^Password-Initial:/,/^[^[:space:]]/ {
/pam_unix\.so/ {
s/\s*nullok//g
}
}' "$config_file"
DEBIAN_FRONTEND=noninteractive pam-auth-update
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure There Are No Accounts With Blank or Null Passwords
[ref]ruleCheck the "/etc/shadow" file for blank passwords with the
following command:
$ sudo awk -F: '!$2 {print $1}' /etc/shadow
If the command returns any results, this is a finding.
Configure all accounts on the system to have a password or lock
the account with the following commands:
Perform a password reset:
$ sudo passwd [username]
Lock an account:
$ sudo passwd -l [username] Warning:
Note that this rule is not applicable for systems running within a container. Having user with empty password within a container is not considered a risk, because it should not be possible to directly login into a container anyway. Rationale:If an account has an empty password, anyone could log in and
run commands with the privileges of that account. Accounts with
empty passwords should never be used in operational environments. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-611065
- NIST-800-53-CM-6(b)
- NIST-800-53-CM-6.1(iv)
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.2
- high_severity
- low_complexity
- low_disruption
- no_empty_passwords_etc_shadow
- no_reboot_needed
- restrict_strategy
- name: Collect users with no password
command: |
awk -F: '!$2 {print $1}' /etc/shadow
register: users_nopasswd
changed_when: false
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-611065
- NIST-800-53-CM-6(b)
- NIST-800-53-CM-6.1(iv)
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.2
- high_severity
- low_complexity
- low_disruption
- no_empty_passwords_etc_shadow
- no_reboot_needed
- restrict_strategy
- name: Lock users with no password
command: |
passwd -l {{ item }}
with_items: '{{ users_nopasswd.stdout_lines }}'
when:
- '"linux-base" in ansible_facts.packages'
- users_nopasswd is not skipped and users_nopasswd.stdout_lines | length > 0
tags:
- DISA-STIG-UBTU-22-611065
- NIST-800-53-CM-6(b)
- NIST-800-53-CM-6.1(iv)
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.2
- high_severity
- low_complexity
- low_disruption
- no_empty_passwords_etc_shadow
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
readarray -t users_with_empty_pass < <(sudo awk -F: '!$2 {print $1}' /etc/shadow)
for user_with_empty_pass in "${users_with_empty_pass[@]}"
do
passwd -l $user_with_empty_pass
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Restrict Root Logins
[ref]groupDirect root logins should be allowed only for emergency use.
In normal situations, the administrator should access the system
via a unique unprivileged account, and then use su or sudo to execute
privileged commands. Discouraging administrators from accessing the
root account directly ensures an audit trail in organizations with
multiple administrators. Locking down the channels through which
root can connect directly also reduces opportunities for
password-guessing against the root account. The login program
uses the file /etc/securetty to determine which interfaces
should allow root logins.
The virtual devices /dev/console
and /dev/tty* represent the system consoles (accessible via
the Ctrl-Alt-F1 through Ctrl-Alt-F6 keyboard sequences on a default
installation). The default securetty file also contains /dev/vc/* .
These are likely to be deprecated in most environments, but may be retained
for compatibility. Root should also be prohibited from connecting
via network protocols. Other sections of this document
include guidance describing how to prevent root from logging in via SSH. |
contains 1 rule |
Direct root Logins Are Not Allowed
[ref]ruleConfigure the operating system to prevent direct logins to the
root account by performing the following operations:
$ sudo passwd -l root Rationale:Disabling direct root logins ensures proper accountability and
multifactor authentication to privileged accounts. |
Secure Session Configuration Files for Login Accounts
[ref]groupWhen a user logs into a Unix account, the system
configures the user's session by reading a number of files. Many of
these files are located in the user's home directory, and may have
weak permissions as a result of user error or misconfiguration. If
an attacker can modify or even read certain types of account
configuration information, they can often gain full access to the
affected user's account. Therefore, it is important to test and
correct configuration file permissions for interactive accounts,
particularly those of privileged users such as root or system
administrators. |
contains 3 rules |
Ensure that Users Have Sensible Umask Values
[ref]groupThe umask setting controls the default permissions
for the creation of new files.
With a default umask setting of 077, files and directories
created by users will not be readable by any other user on the
system. Users who wish to make specific files group- or
world-readable can accomplish this by using the chmod command.
Additionally, users can make all their files readable to their
group by default by setting a umask of 027 in their shell
configuration files. If default per-user groups exist (that is, if
every user has a default group whose name is the same as that
user's username and whose only member is the user), then it may
even be safe for users to select a umask of 007, making it very
easy to intentionally share files with groups of which the user is
a member.
|
contains 1 rule |
Ensure the Default Umask is Set Correctly in login.defs
[ref]ruleTo ensure the default umask controlled by /etc/login.defs is set properly,
add or correct the UMASK setting in /etc/login.defs to read as follows:
UMASK 077 Rationale:The umask value influences the permissions assigned to files when they are created.
A misconfigured umask value could result in files with excessive permissions that can be read and
written to by unauthorized users. References:
R36, 5.5.4, 11, 18, 3, 9, APO13.01, BAI03.01, BAI03.02, BAI03.03, BAI10.01, BAI10.02, BAI10.03, BAI10.05, CCI-000366, 4.3.4.3.2, 4.3.4.3.3, SR 7.6, A.12.1.2, A.12.5.1, A.12.6.2, A.14.1.1, A.14.2.1, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.5, A.6.1.5, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, AC-6(1), CM-6(a), PR.IP-1, PR.IP-2, SRG-OS-000480-GPOS-00228, UBTU-22-412035, SV-260555r991590_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-412035
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-6(a)
- accounts_umask_etc_login_defs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_accounts_user_umask # promote to variable
set_fact:
var_accounts_user_umask: !!str 077
tags:
- always
- name: Check if UMASK is already set
ansible.builtin.lineinfile:
path: /etc/login.defs
regexp: ^(\s*)UMASK\s+.*
state: absent
check_mode: true
changed_when: false
register: result_umask_is_set
when: '"login" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-412035
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-6(a)
- accounts_umask_etc_login_defs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Replace user UMASK in /etc/login.defs
ansible.builtin.replace:
path: /etc/login.defs
regexp: ^(\s*)UMASK(\s+).*
replace: \g<1>UMASK\g<2>{{ var_accounts_user_umask }}
when:
- '"login" in ansible_facts.packages'
- result_umask_is_set.found > 0
tags:
- DISA-STIG-UBTU-22-412035
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-6(a)
- accounts_umask_etc_login_defs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Ensure the Default UMASK is Appended Correctly
ansible.builtin.lineinfile:
create: true
path: /etc/login.defs
line: UMASK {{ var_accounts_user_umask }}
when:
- '"login" in ansible_facts.packages'
- result_umask_is_set.found == 0
tags:
- DISA-STIG-UBTU-22-412035
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-6(a)
- accounts_umask_etc_login_defs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'login' 2>/dev/null | grep -q '^installed'; then
var_accounts_user_umask='077'
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^UMASK")
# shellcheck disable=SC2059
printf -v formatted_output "%s %s" "$stripped_key" "$var_accounts_user_umask"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^UMASK\\>" "/etc/login.defs"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^UMASK\\>.*/$escaped_formatted_output/gi" "/etc/login.defs"
else
if [[ -s "/etc/login.defs" ]] && [[ -n "$(tail -c 1 -- "/etc/login.defs" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/login.defs"
fi
printf '%s\n' "$formatted_output" >> "/etc/login.defs"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Limit the Number of Concurrent Login Sessions Allowed Per User
[ref]ruleLimiting the number of allowed users and sessions per user can limit risks related to Denial of
Service attacks. This addresses concurrent sessions for a single account and does not address
concurrent sessions by a single user via multiple accounts. To set the number of concurrent
sessions per user add the following line in /etc/security/limits.conf or
a file under /etc/security/limits.d/ :
* hard maxlogins 10 Rationale:Limiting simultaneous user logins can insulate the system from denial of service
problems caused by excessive logins. Automated login processes operating improperly or
maliciously may result in an exceptional number of simultaneous login sessions. References:
14, 15, 18, 9, 5.5.2.2, DSS01.05, DSS05.02, CCI-000054, 4.3.3.4, SR 3.1, SR 3.8, A.13.1.1, A.13.1.3, A.13.2.1, A.14.1.2, A.14.1.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, AC-10, CM-6(a), PR.AC-5, SRG-OS-000027-GPOS-00008, UBTU-22-412020, SV-260552r958398_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'libpam-runtime' 2>/dev/null | grep -q '^installed'; then
var_accounts_max_concurrent_login_sessions='10'
if grep -q '^[^#]*\<maxlogins\>' /etc/security/limits.d/*.conf; then
sed -i "/^[^#]*\<maxlogins\>/ s/maxlogins.*/maxlogins $var_accounts_max_concurrent_login_sessions/" /etc/security/limits.d/*.conf
elif grep -q '^[^#]*\<maxlogins\>' /etc/security/limits.conf; then
sed -i "/^[^#]*\<maxlogins\>/ s/maxlogins.*/maxlogins $var_accounts_max_concurrent_login_sessions/" /etc/security/limits.conf
else
echo "* hard maxlogins $var_accounts_max_concurrent_login_sessions" >> /etc/security/limits.conf
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Set Interactive Session Timeout
[ref]ruleSetting the TMOUT option in /etc/profile ensures that
all user sessions will terminate based on inactivity.
The value of TMOUT should be exported and read only.
The TMOUT
setting in a file loaded by /etc/profile , e.g.
/etc/profile.d/tmout.sh should read as follows:
TMOUT=900
readonly TMOUT
export TMOUTRationale:Terminating an idle session within a short time period reduces
the window of opportunity for unauthorized personnel to take control of a
management session enabled on the console or console port that has been
left unattended. References:
R32, 5.5.5, 1, 12, 15, 16, DSS05.04, DSS05.10, DSS06.10, 3.1.11, CCI-000057, CCI-001133, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9, A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3, CIP-004-6 R2.2.3, CIP-007-3 R5.1, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3, AC-12, SC-10, AC-2(5), CM-6(a), PR.AC-7, 8.6.1, 8.6, SRG-OS-000163-GPOS-00072, SRG-OS-000029-GPOS-00010, UBTU-22-412030, SV-260554r958636_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-412030
- NIST-800-171-3.1.11
- NIST-800-53-AC-12
- NIST-800-53-AC-2(5)
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-10
- PCI-DSSv4-8.6
- PCI-DSSv4-8.6.1
- accounts_tmout
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_accounts_tmout # promote to variable
set_fact:
var_accounts_tmout: !!str 900
tags:
- always
- name: Correct any occurrence of TMOUT in /etc/profile
replace:
path: /etc/profile
regexp: ^[^#].*TMOUT=.*
replace: typeset -xr TMOUT={{ var_accounts_tmout }}
register: profile_replaced
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-412030
- NIST-800-171-3.1.11
- NIST-800-53-AC-12
- NIST-800-53-AC-2(5)
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-10
- PCI-DSSv4-8.6
- PCI-DSSv4-8.6.1
- accounts_tmout
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Set Interactive Session Timeout
lineinfile:
path: /etc/profile.d/tmout.sh
create: true
regexp: TMOUT=
line: typeset -xr TMOUT={{ var_accounts_tmout }}
state: present
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-412030
- NIST-800-171-3.1.11
- NIST-800-53-AC-12
- NIST-800-53-AC-2(5)
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-10
- PCI-DSSv4-8.6
- PCI-DSSv4-8.6.1
- accounts_tmout
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
var_accounts_tmout='900'
# if 0, no occurence of tmout found, if 1, occurence found
tmout_found=0
for f in /etc/bash.bashrc /etc/profile /etc/profile.d/*.sh; do
if grep --silent '^\s*TMOUT' $f; then
sed -i -E "s/^(\s*)TMOUT\s*=\s*(\w|\$)*(.*)$/\1TMOUT=$var_accounts_tmout\3/g" $f
tmout_found=1
if ! grep --silent '^\s*readonly TMOUT' $f ; then
echo "readonly TMOUT" >> $f
fi
if ! grep --silent '^\s*export TMOUT' $f ; then
echo "export TMOUT" >> $f
fi
fi
done
if [ $tmout_found -eq 0 ]; then
echo -e "\n# Set TMOUT to $var_accounts_tmout per security requirements" >> /etc/profile.d/tmout.sh
echo "TMOUT=$var_accounts_tmout" >> /etc/profile.d/tmout.sh
echo "readonly TMOUT" >> /etc/profile.d/tmout.sh
echo "export TMOUT" >> /etc/profile.d/tmout.sh
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
AppArmor
[ref]groupMany security vulnerabilities result from bugs in trusted programs. A trusted
program runs with privileges that attackers want to possess. The program fails
to keep that trust if there is a bug in the program that allows the attacker to
acquire said privilege.
AppArmor® is an application security solution designed specifically to apply
privilege confinement to suspect programs. AppArmor allows the administrator to
specify the domain of activities the program can perform by developing a
security profile. A security profile is a listing of files that the program may
access and the operations the program may perform. AppArmor secures
applications by enforcing good application behavior without relying on attack
signatures, so it can prevent attacks even if previously unknown
vulnerabilities are being exploited. |
contains 2 rules |
Ensure AppArmor is installed
[ref]ruleAppArmor provide Mandatory Access Controls. Rationale:Without a Mandatory Access Control system installed only the default
Discretionary Access Control system will be available. References:
R45, 1.6.1.1, CCI-001764, CCI-001774, CCI-002165, CCI-002235, SRG-OS-000368-GPOS-00154, SRG-OS-000312-GPOS-00122, SRG-OS-000312-GPOS-00123, SRG-OS-000312-GPOS-00124, SRG-OS-000324-GPOS-00125, SRG-OS-000370-GPOS-00155, UBTU-22-431010, SV-260556r958702_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Ensure apparmor is installed
package:
name: apparmor
state: present
when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- DISA-STIG-UBTU-22-431010
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_apparmor_installed
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_apparmor
class install_apparmor {
package { 'apparmor':
ensure => 'installed',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "apparmor"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[[packages]]
name = "apparmor"
version = "*"
|
Ensure AppArmor is Active and Configured
[ref]ruleVerify that the Apparmor tool is configured to
control whitelisted applications and user home directory access
control.
The apparmor service can be enabled with the following command:
$ sudo systemctl enable apparmor.service Rationale:Using a whitelist provides a configuration management method for allowing
the execution of only authorized software. Using only authorized software
decreases risk by limiting the number of potential vulnerabilities.
The organization must identify authorized software programs and permit
execution of authorized software by adding each authorized program to the
"pam_apparmor" exception policy. The process used to identify software
programs that are authorized to execute on organizational information
systems is commonly referred to as whitelisting.
Verification of whitelisted software occurs prior to execution or at system
startup.
Users' home directories/folders may contain information of a sensitive
nature. Nonprivileged users should coordinate any sharing of information
with a System Administrator (SA) through shared resources.
Apparmor can confine users to their home directory, not allowing them to
make any changes outside of their own home directories. Confining users to
their home directory will minimize the risk of sharing information. References:
R45, CCI-001764, CCI-001774, CCI-002165, CCI-002233, CCI-002235, AC-3(4), AC-6(8), AC-6(10), CM-7(5)(b), CM-7(2), SC-7(21), CM-6(a), SRG-OS-000312-GPOS-00122, SRG-OS-000312-GPOS-00123, SRG-OS-000312-GPOS-00124, SRG-OS-000324-GPOS-00125, SRG-OS-000326-GPOS-00126, SRG-OS-000370-GPOS-00155, SRG-OS-000480-GPOS-00230, SRG-OS-000480-GPOS-00227, SRG-OS-000480-GPOS-00231, SRG-OS-000480-GPOS-00232, UBTU-22-431015, SV-260557r958804_rule Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include enable_apparmor
class enable_apparmor {
service {'apparmor':
enable => true,
ensure => 'running',
}
}
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
# Enable apparmor
/usr/bin/systemctl enable "apparmor"
if [[ $(/usr/bin/systemctl is-system-running) != "offline" ]]; then
/usr/bin/systemctl start "apparmor"
fi
# The service may not be running because it has been started and failed,
# so let's reset the state so OVAL checks pass.
# Service should be 'inactive', not 'failed' after reboot though.
if /usr/bin/systemctl --failed | grep -q "apparmor"; then
/usr/bin/systemctl reset-failed "apparmor"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[customizations.services]
enabled = ["apparmor"]
|
GRUB2 bootloader configuration
[ref]groupDuring the boot process, the boot loader is
responsible for starting the execution of the kernel and passing
options to it. The boot loader allows for the selection of
different kernels - possibly on different partitions or media.
The default Ubuntu 22.04 boot loader for x86 systems is called GRUB2.
Options it can pass to the kernel include single-user mode, which
provides root access without any authentication, and the ability to
disable SELinux. To prevent local users from modifying the boot
parameters and endangering security, protect the boot loader configuration
with a password and ensure its configuration file's permissions
are set properly. |
contains 2 rules |
Non-UEFI GRUB2 bootloader configuration
[ref]groupNon-UEFI GRUB2 bootloader configuration |
contains 1 rule |
Set Boot Loader Password in grub2
[ref]ruleThe grub2 boot loader should have a superuser account and password
protection enabled to protect boot-time settings.
Since plaintext passwords are a security risk, generate a hash for the password
by running the following command:
# grub2-mkpasswd-pbkdf2
When prompted, enter the password that was selected.
Using the hash from the output, modify the /etc/grub.d/40_custom
file with the following content:
set superusers="boot"
password_pbkdf2 boot grub.pbkdf2.sha512.VeryLongString
NOTE: the bootloader superuser account and password MUST differ from the
root account and password.
Once the superuser password has been added,
update the
grub.cfg file by running:
update-grub Warning:
To prevent hard-coded passwords, automatic remediation of this control is not available. Remediation
must be automated as a component of machine provisioning, or followed manually as outlined above.
Also, do NOT manually add the superuser account and password to the
grub.cfg file as the grub2-mkconfig command overwrites this file. Rationale:Password protection on the boot loader configuration ensures
users with physical access cannot trivially alter
important bootloader settings. These include which kernel to use,
and whether to enter single-user mode. References:
R5, 1.4.1, 1, 11, 12, 14, 15, 16, 18, 3, 5, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.06, DSS06.10, 3.4.5, CCI-000213, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, CM-6(a), PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.PT-3, SRG-OS-000080-GPOS-00048, UBTU-22-212010, SV-260470r958472_rule |
UEFI GRUB2 bootloader configuration
[ref]groupUEFI GRUB2 bootloader configuration Warning:
UEFI generally uses vfat file systems, which does not support Unix-style permissions
managed by chmod command. In this case, in order to change file permissions for files
within /boot/efi it is necessary to update the mount options in /etc/fstab file and
reboot the system. |
contains 1 rule |
Set the UEFI Boot Loader Password
[ref]ruleThe grub2 boot loader should have a superuser account and password
protection enabled to protect boot-time settings.
Since plaintext passwords are a security risk, generate a hash for the password
by running the following command:
# grub2-mkpasswd-pbkdf2
When prompted, enter the password that was selected.
Using the hash from the output, modify the /etc/grub.d/40_custom
file with the following content:
set superusers="boot"
password_pbkdf2 boot grub.pbkdf2.sha512.VeryLongString
NOTE: the bootloader superuser account and password MUST differ from the
root account and password.
Once the superuser password has been added,
update the
grub.cfg file by running:
update-grub Warning:
To prevent hard-coded passwords, automatic remediation of this control is not available. Remediation
must be automated as a component of machine provisioning, or followed manually as outlined above.
Also, do NOT manually add the superuser account and password to the
grub.cfg file as the grub2-mkconfig command overwrites this file. Rationale:Password protection on the boot loader configuration ensures
users with physical access cannot trivially alter
important bootloader settings. These include which kernel to use,
and whether to enter single-user mode. References:
R5, 1.4.1, 11, 12, 14, 15, 16, 18, 3, 5, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.03, DSS06.06, 3.4.5, CCI-000213, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CM-6(a), PR.AC-4, PR.AC-6, PR.PT-3, FIA_UAU.1, SRG-OS-000080-GPOS-00048, UBTU-22-212010, SV-260470r958472_rule |
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 Ubuntu 22.04, 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 12 rules |
Ensure Proper Configuration of Log Files
[ref]groupThe file /etc/rsyslog.conf controls where log message are written.
These are controlled by lines called rules, which consist of a
selector and an action.
These rules are often customized depending on the role of the system, the
requirements of the environment, and whatever may enable
the administrator to most effectively make use of log data.
The default rules in Ubuntu 22.04 are:
*.info;mail.none;authpriv.none;cron.none /var/log/messages
authpriv.* /var/log/secure
mail.* -/var/log/maillog
cron.* /var/log/cron
*.emerg *
uucp,news.crit /var/log/spooler
local7.* /var/log/boot.log
See the man page rsyslog.conf(5) for more information.
Note that the rsyslog daemon can be configured to use a timestamp format that
some log processing programs may not understand. If this occurs,
edit the file /etc/rsyslog.conf and add or edit the following line:
$ ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat |
contains 1 rule |
Ensure remote access methods are monitored in Rsyslog
[ref]ruleLogging of remote access methods must be implemented to help identify cyber
attacks and ensure ongoing compliance with remote access policies are being
audited and upheld. An examples of a remote access method is the use of the
Remote Desktop Protocol (RDP) from an external, non-organization controlled
network. The /etc/rsyslog.conf or
/etc/rsyslog.d/*.conf file should contain a match for the following
selectors: auth.* , authpriv.* , and daemon.* . If
not, use the following as an example configuration:
auth.*;authpriv.* /var/log/secure
daemon.* /var/log/messages
Rationale:Logging remote access methods can be used to trace the decrease the risks
associated with remote user access management. It can also be used to spot
cyber attacks and ensure ongoing compliance with organizational policies
surrounding the use of remote access methods. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-652015
- NIST-800-53-AC-17(1)
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- rsyslog_remote_access_monitoring
- name: Ensure remote access methods are monitored in Rsyslog - Set Facts
ansible.builtin.set_fact:
conf_files:
- /etc/rsyslog.d/50-default.conf
remote_methods:
- selector: auth.*
regexp: ^.*auth\.\*.*$
log_path_name: secure
- selector: authpriv.*
regexp: ^.*authpriv\.\*.*$
log_path_name: secure
- selector: daemon.*
regexp: ^.*daemon\.\*.*$
log_path_name: messages
when:
- '"linux-base" in ansible_facts.packages'
- '"rsyslog" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-652015
- NIST-800-53-AC-17(1)
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- rsyslog_remote_access_monitoring
- name: Ensure remote access methods are monitored in Rsyslog - Ensure /etc/rsyslog.d/50-default.conf
Exists
ansible.builtin.file:
path: '{{ conf_files.0 }}'
state: touch
when:
- '"linux-base" in ansible_facts.packages'
- '"rsyslog" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-652015
- NIST-800-53-AC-17(1)
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- rsyslog_remote_access_monitoring
- name: Ensure remote access methods are monitored in Rsyslog - Check for Existing
Values in Conf Files
ansible.builtin.lineinfile:
path: '{{ item.1 }}'
regexp: '{{ item.0.regexp }}'
state: absent
check_mode: true
changed_when: false
register: remote_method_values
loop: '{{ remote_methods|product(conf_files)|list }}'
when:
- '"linux-base" in ansible_facts.packages'
- '"rsyslog" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-652015
- NIST-800-53-AC-17(1)
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- rsyslog_remote_access_monitoring
- name: Ensure remote access methods are monitored in Rsyslog - Configure /etc/rsyslog.d/50-default.conf
With Proper Log Paths
ansible.builtin.lineinfile:
path: /etc/rsyslog.d/50-default.conf
line: '{{ item.item.0.selector }} /var/log/{{ item.item.0.log_path_name }}'
insertafter: ^.*\/var\/log\/secure.*$
create: true
loop: '{{ remote_method_values.results }}'
when:
- '"linux-base" in ansible_facts.packages'
- '"rsyslog" in ansible_facts.packages'
- item.found == 0
tags:
- DISA-STIG-UBTU-22-652015
- NIST-800-53-AC-17(1)
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- rsyslog_remote_access_monitoring
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'rsyslog' 2>/dev/null | grep -q '^installed'; then
if [ ! -f /etc/rsyslog.d/50-default.conf ]; then
# Something is not right, create the file
touch /etc/rsyslog.d/50-default.conf
fi
# Check to see if auth exists
if ! grep -Erq "^auth\.\*,authpriv\.\*" /etc/rsyslog.*; then
echo "auth.*,authpriv.* /var/log/secure" >> /etc/rsyslog.d/50-default.conf
fi
if ! grep -Erq "^daemon\.\*" /etc/rsyslog.*; then
echo "daemon.* /var/log/messages" >> /etc/rsyslog.d/50-default.conf
fi
systemctl restart rsyslog.service
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
systemd-journald
[ref]groupsystemd-journald is a system service that collects and stores
logging data. It creates and maintains structured, indexed
journals based on logging information that is received from a
variety of sources.
For more information on systemd-journald and additional systemd-journald configuration options, see
https://systemd.io/. |
contains 9 rules |
Verify group-owner of system journal directories
[ref]ruleVerify the /run/log/journal and /var/log/journal directories are group-owned by
"systemd-journal" by using the following command:
$ sudo find /run/log/journal /var/log/journal -type d -exec stat -c "%n %G" {} \;
If any output returned is not owned by "systemd-journal", this is a finding.Rationale:Only authorized personnel should be aware of errors and the details of the errors.
Error messages are an indicator of an organization's operational state or can identify
the operating system or platform. Additionally, personally identifiable information
(PII) and operational information must not be revealed through error messages to
unauthorized personnel or their designated representatives. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-232085
- configure_strategy
- dir_groupowner_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /run/log/journal/ recursively
file:
path: /run/log/journal/
state: directory
recurse: true
group: systemd-journal
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232085
- configure_strategy
- dir_groupowner_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /var/log/journal/ recursively
file:
path: /var/log/journal/
state: directory
recurse: true
group: systemd-journal
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232085
- configure_strategy
- dir_groupowner_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
TMPFILES_CONF="/usr/lib/tmpfiles.d/systemd.conf"
if ! grep -q 'Z /var/log/journal ~2750 root systemd-journal - -' "$TMPFILES_CONF"; then
if grep -qP "^[zZ][+]*\s+\/var\/log\/journal" "$TMPFILES_CONF"; then
sed -i --follow-symlinks "s/\(^[zZ][+]*\)\(\s\+\/var\/log\/journal.*\)/# \1\2/" "$TMPFILES_CONF"
fi
echo "Z /var/log/journal ~2750 root systemd-journal - -" >>"$TMPFILES_CONF"
fi
if ! grep -q 'Z /run/log/journal ~2750 root systemd-journal - -' "$TMPFILES_CONF"; then
if grep -qP "^[zZ][+]*\s+\/run\/log\/journal" "$TMPFILES_CONF"; then
sed -i --follow-symlinks "s/\(^[zZ][+]*\)\(\s\+\/run\/log\/journal.*\)/# \1\2/" "$TMPFILES_CONF"
fi
echo "Z /run/log/journal ~2750 root systemd-journal - -" >>"$TMPFILES_CONF"
fi
systemd-tmpfiles --create
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Verify owner of system journal directories
[ref]ruleVerify the /run/log/journal and /var/log/journal directories are owned by
"root" by using the following command:
$ sudo find /run/log/journal /var/log/journal -type d -exec stat -c "%n %U" {} \;
If any output returned is not owned by "root", this is a finding.Rationale:Only authorized personnel should be aware of errors and the details of the errors.
Error messages are an indicator of an organization's operational state or can identify
the operating system or platform. Additionally, personally identifiable information
(PII) and operational information must not be revealed through error messages to
unauthorized personnel or their designated representatives. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-232080
- configure_strategy
- dir_owner_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /run/log/journal/ recursively
file:
path: /run/log/journal/
state: directory
recurse: true
owner: '0'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232080
- configure_strategy
- dir_owner_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /var/log/journal/ recursively
file:
path: /var/log/journal/
state: directory
recurse: true
owner: '0'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232080
- configure_strategy
- dir_owner_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
TMPFILES_CONF="/usr/lib/tmpfiles.d/systemd.conf"
if ! grep -q 'Z /var/log/journal ~2750 root systemd-journal - -' "$TMPFILES_CONF"; then
if grep -qP "^[zZ][+]*\s+\/var\/log\/journal" "$TMPFILES_CONF"; then
sed -i --follow-symlinks "s/\(^[zZ][+]*\)\(\s\+\/var\/log\/journal.*\)/# \1\2/" "$TMPFILES_CONF"
fi
echo "Z /var/log/journal ~2750 root systemd-journal - -" >>"$TMPFILES_CONF"
fi
if ! grep -q 'Z /run/log/journal ~2750 root systemd-journal - -' "$TMPFILES_CONF"; then
if grep -qP "^[zZ][+]*\s+\/run\/log\/journal" "$TMPFILES_CONF"; then
sed -i --follow-symlinks "s/\(^[zZ][+]*\)\(\s\+\/run\/log\/journal.*\)/# \1\2/" "$TMPFILES_CONF"
fi
echo "Z /run/log/journal ~2750 root systemd-journal - -" >>"$TMPFILES_CONF"
fi
systemd-tmpfiles --create
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Verify Permissions on the system journal directories
[ref]ruleVerify the /run/log/journal and /var/log/journal directories have
permissions set to "2750" or less permissive by using the following command:
$ sudo find /run/log/journal /var/log/journal -type d -exec stat -c "%n %a" {} \;
If any output returned has a permission set greater than "2750", this is a finding.Rationale:Any operating system providing too much information in error messages risks
compromising the data and security of the structure, and content of error messages
needs to be carefully considered by the organization. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-232027
- configure_strategy
- dir_permissions_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /run/log/journal/ file(s) recursively
command: 'find -H /run/log/journal/ -perm /u+s,g+w,o+xwrt -type d '
register: files_found
changed_when: false
failed_when: false
check_mode: false
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232027
- configure_strategy
- dir_permissions_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /run/log/journal/ file(s)
file:
path: '{{ item }}'
mode: u-s,g-w,o-xwrt
state: directory
with_items:
- '{{ files_found.stdout_lines }}'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232027
- configure_strategy
- dir_permissions_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /var/log/journal/ file(s) recursively
command: 'find -H /var/log/journal/ -perm /u+s,g+w,o+xwrt -type d '
register: files_found
changed_when: false
failed_when: false
check_mode: false
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232027
- configure_strategy
- dir_permissions_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /var/log/journal/ file(s)
file:
path: '{{ item }}'
mode: u-s,g-w,o-xwrt
state: directory
with_items:
- '{{ files_found.stdout_lines }}'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232027
- configure_strategy
- dir_permissions_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
TMPFILES_CONF="/usr/lib/tmpfiles.d/systemd.conf"
if ! grep -q 'Z /var/log/journal ~2750 root systemd-journal - -' "$TMPFILES_CONF"; then
if grep -qP "^[zZ][+]*\s+\/var\/log\/journal" "$TMPFILES_CONF"; then
sed -i --follow-symlinks "s/\(^[zZ][+]*\)\(\s\+\/var\/log\/journal.*\)/# \1\2/" "$TMPFILES_CONF"
fi
echo "Z /var/log/journal ~2750 root systemd-journal - -" >>"$TMPFILES_CONF"
fi
if ! grep -q 'Z /run/log/journal ~2750 root systemd-journal - -' "$TMPFILES_CONF"; then
if grep -qP "^[zZ][+]*\s+\/run\/log\/journal" "$TMPFILES_CONF"; then
sed -i --follow-symlinks "s/\(^[zZ][+]*\)\(\s\+\/run\/log\/journal.*\)/# \1\2/" "$TMPFILES_CONF"
fi
echo "Z /run/log/journal ~2750 root systemd-journal - -" >>"$TMPFILES_CONF"
fi
systemd-tmpfiles --create
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Verify Groupowner on the journalctl command
[ref]ruleVerify that the "journalctl" command is group-owned by "root" by
using the following command:
$ sudo find /usr/bin/journalctl -exec stat -c "%n %G" {} \;
If any output returned is not owned by "root", this is a finding.Rationale:Only authorized personnel should be aware of errors and the details of the errors.
Error messages are an indicator of an organization's operational state or can
identify the operating system or platform. Additionally, personally identifiable
information (PII) and operational information must not be revealed through error
messages to unauthorized personnel or their designated representatives. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-232105
- configure_strategy
- file_groupowner_journalctl
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /usr/bin/journalctl
stat:
path: /usr/bin/journalctl
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232105
- configure_strategy
- file_groupowner_journalctl
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner 0 on /usr/bin/journalctl
file:
path: /usr/bin/journalctl
group: '0'
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232105
- configure_strategy
- file_groupowner_journalctl
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
chgrp 0 /usr/bin/journalctl
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Verify Group Who Owns the system journal
[ref]ruleVerify the /run/log/journal and /var/log/journal files are group-owned by
"systemd-journal" by using the following command:
$ sudo find /run/log/journal /var/log/journal -type f -exec stat -c "%n %G" {} \;
If any output returned is not group-owned by "systemd-journal", this is a finding.Rationale:Only authorized personnel should be aware of errors and the details of the errors.
Error messages are an indicator of an organization's operational state or can
identify the operating system or platform. Additionally, personally identifiable
information (PII) and operational information must not be revealed through error
messages to unauthorized personnel or their designated representatives. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-232095
- configure_strategy
- file_groupowner_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /run/log/journal/ file(s) matching ^.*$ recursively
command: find -H /run/log/journal/ -type f ! -group systemd-journal -regextype
posix-extended -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232095
- configure_strategy
- file_groupowner_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /run/log/journal/ file(s) matching ^.*$
file:
path: '{{ item }}'
group: systemd-journal
state: file
with_items:
- '{{ files_found.stdout_lines }}'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232095
- configure_strategy
- file_groupowner_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /var/log/journal/ file(s) matching ^.*$ recursively
command: find -H /var/log/journal/ -type f ! -group systemd-journal -regextype
posix-extended -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232095
- configure_strategy
- file_groupowner_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /var/log/journal/ file(s) matching ^.*$
file:
path: '{{ item }}'
group: systemd-journal
state: file
with_items:
- '{{ files_found.stdout_lines }}'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232095
- configure_strategy
- file_groupowner_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
TMPFILES_CONF="/usr/lib/tmpfiles.d/systemd.conf"
if ! grep -q 'Z /var/log/journal ~2750 root systemd-journal - -' "$TMPFILES_CONF"; then
if grep -qP "^[zZ][+]*\s+\/var\/log\/journal" "$TMPFILES_CONF"; then
sed -i --follow-symlinks "s/\(^[zZ][+]*\)\(\s\+\/var\/log\/journal.*\)/# \1\2/" "$TMPFILES_CONF"
fi
echo "Z /var/log/journal ~2750 root systemd-journal - -" >>"$TMPFILES_CONF"
fi
if ! grep -q 'Z /run/log/journal ~2750 root systemd-journal - -' "$TMPFILES_CONF"; then
if grep -qP "^[zZ][+]*\s+\/run\/log\/journal" "$TMPFILES_CONF"; then
sed -i --follow-symlinks "s/\(^[zZ][+]*\)\(\s\+\/run\/log\/journal.*\)/# \1\2/" "$TMPFILES_CONF"
fi
echo "Z /run/log/journal ~2750 root systemd-journal - -" >>"$TMPFILES_CONF"
fi
systemd-tmpfiles --create
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Verify Owner on the journalctl Command
[ref]ruleVerify that the "journalctl" command is owned by "root" by
using the following command:
$ sudo find /usr/bin/journalctl -exec stat -c "%n %U" {} \;
If any output returned is not owned by "root", this is a finding.Rationale:Only authorized personnel should be aware of errors and the details of the errors.
Error messages are an indicator of an organization's operational state or can
identify the operating system or platform. Additionally, personally identifiable
information (PII) and operational information must not be revealed through error
messages to unauthorized personnel or their designated representatives. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-232100
- configure_strategy
- file_owner_journalctl
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /usr/bin/journalctl
stat:
path: /usr/bin/journalctl
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232100
- configure_strategy
- file_owner_journalctl
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner 0 on /usr/bin/journalctl
file:
path: /usr/bin/journalctl
owner: '0'
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232100
- configure_strategy
- file_owner_journalctl
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
chown 0 /usr/bin/journalctl
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Verify Owner on the system journal
[ref]ruleVerify the /run/log/journal and /var/log/journal files are owned by
"root" by using the following command:
$ sudo find /run/log/journal /var/log/journal -type f -exec stat -c "%n %U" {} \;
If any output returned is not owned by "root", this is a finding.Rationale:Only authorized personnel should be aware of errors and the details of the errors.
Error messages are an indicator of an organization's operational state or can
identify the operating system or platform. Additionally, personally identifiable
information (PII) and operational information must not be revealed through error
messages to unauthorized personnel or their designated representatives. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-232090
- configure_strategy
- file_owner_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /run/log/journal/ file(s) matching ^.*$ recursively
command: find -H /run/log/journal/ -type f ! -uid 0 -regextype posix-extended -regex
"^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232090
- configure_strategy
- file_owner_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on /run/log/journal/ file(s) matching ^.*$
file:
path: '{{ item }}'
owner: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232090
- configure_strategy
- file_owner_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /var/log/journal/ file(s) matching ^.*$ recursively
command: find -H /var/log/journal/ -type f ! -uid 0 -regextype posix-extended -regex
"^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232090
- configure_strategy
- file_owner_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on /var/log/journal/ file(s) matching ^.*$
file:
path: '{{ item }}'
owner: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232090
- configure_strategy
- file_owner_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
TMPFILES_CONF="/usr/lib/tmpfiles.d/systemd.conf"
if ! grep -q 'Z /var/log/journal ~2750 root systemd-journal - -' "$TMPFILES_CONF"; then
if grep -qP "^[zZ][+]*\s+\/var\/log\/journal" "$TMPFILES_CONF"; then
sed -i --follow-symlinks "s/\(^[zZ][+]*\)\(\s\+\/var\/log\/journal.*\)/# \1\2/" "$TMPFILES_CONF"
fi
echo "Z /var/log/journal ~2750 root systemd-journal - -" >>"$TMPFILES_CONF"
fi
if ! grep -q 'Z /run/log/journal ~2750 root systemd-journal - -' "$TMPFILES_CONF"; then
if grep -qP "^[zZ][+]*\s+\/run\/log\/journal" "$TMPFILES_CONF"; then
sed -i --follow-symlinks "s/\(^[zZ][+]*\)\(\s\+\/run\/log\/journal.*\)/# \1\2/" "$TMPFILES_CONF"
fi
echo "Z /run/log/journal ~2750 root systemd-journal - -" >>"$TMPFILES_CONF"
fi
systemd-tmpfiles --create
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Verify Permissions on the journal command
[ref]ruleVerify that the "journalctl" command has a permission set of "740" by
using the following command:
$ sudo find /usr/bin/journalctl -exec stat -c "%n %a" {} \;
If "journalctl" is not set to "740", this is a finding.Rationale:Any operating system providing too much information in error messages risks
compromising the data and security of the structure, and content of error messages
needs to be carefully considered by the organization. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-232140
- configure_strategy
- file_permissions_journalctl
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /usr/bin/journalctl
stat:
path: /usr/bin/journalctl
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232140
- configure_strategy
- file_permissions_journalctl
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-s,g-xws,o-xwrt on /usr/bin/journalctl
file:
path: /usr/bin/journalctl
mode: u-s,g-xws,o-xwrt
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232140
- configure_strategy
- file_permissions_journalctl
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
chmod u-s,g-xws,o-xwrt /usr/bin/journalctl
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Verify Permissions on the system journal
[ref]ruleVerify all files in the /run/log/journal and /var/log/journal directories have
permissions set to "640" or less permissive by using the following command:
$ sudo find /run/log/journal /var/log/journal -type f -exec stat -c "%n %a" {} \;
If any output returned has a permission set greater than "640", this is a finding.Rationale:Any operating system providing too much information in error messages risks
compromising the data and security of the structure, and content of error messages
needs to be carefully considered by the organization. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-232027
- configure_strategy
- file_permissions_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /run/log/journal/ file(s) recursively
command: find -H /run/log/journal/ -perm /u+xs,g+xws,o+xwrt -type f -regextype
posix-extended -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232027
- configure_strategy
- file_permissions_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /run/log/journal/ file(s)
file:
path: '{{ item }}'
mode: u-xs,g-xws,o-xwrt
state: file
with_items:
- '{{ files_found.stdout_lines }}'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232027
- configure_strategy
- file_permissions_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /var/log/journal/ file(s) recursively
command: find -H /var/log/journal/ -perm /u+xs,g+xws,o+xwrt -type f -regextype
posix-extended -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232027
- configure_strategy
- file_permissions_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /var/log/journal/ file(s)
file:
path: '{{ item }}'
mode: u-xs,g-xws,o-xwrt
state: file
with_items:
- '{{ files_found.stdout_lines }}'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232027
- configure_strategy
- file_permissions_system_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
TMPFILES_CONF="/usr/lib/tmpfiles.d/systemd.conf"
if ! grep -q 'Z /var/log/journal ~2750 root systemd-journal - -' "$TMPFILES_CONF"; then
if grep -qP "^[zZ][+]*\s+\/var\/log\/journal" "$TMPFILES_CONF"; then
sed -i --follow-symlinks "s/\(^[zZ][+]*\)\(\s\+\/var\/log\/journal.*\)/# \1\2/" "$TMPFILES_CONF"
fi
echo "Z /var/log/journal ~2750 root systemd-journal - -" >>"$TMPFILES_CONF"
fi
if ! grep -q 'Z /run/log/journal ~2750 root systemd-journal - -' "$TMPFILES_CONF"; then
if grep -qP "^[zZ][+]*\s+\/run\/log\/journal" "$TMPFILES_CONF"; then
sed -i --follow-symlinks "s/\(^[zZ][+]*\)\(\s\+\/run\/log\/journal.*\)/# \1\2/" "$TMPFILES_CONF"
fi
echo "Z /run/log/journal ~2750 root systemd-journal - -" >>"$TMPFILES_CONF"
fi
systemd-tmpfiles --create
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Enable rsyslog Service
[ref]ruleThe rsyslog service provides syslog-style logging by default on Ubuntu 22.04.
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. References:
4.2.2.2, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO13.01, BAI03.05, BAI04.04, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, CCI-000366, 164.312(a)(2)(ii), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, SR 7.1, SR 7.2, A.12.1.3, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, A.17.2.1, CM-6(a), AU-4(1), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.DS-4, PR.PT-1, SRG-OS-000480-GPOS-00227, UBTU-22-652010, SV-260588r991562_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-652010
- NIST-800-53-AU-4(1)
- NIST-800-53-CM-6(a)
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_rsyslog_enabled
- name: Enable rsyslog Service - Enable service rsyslog
block:
- name: Gather the package facts
package_facts:
manager: auto
- name: Enable rsyslog Service - Enable Service rsyslog
ansible.builtin.systemd:
name: rsyslog
enabled: true
state: started
masked: false
when:
- '"rsyslog" in ansible_facts.packages'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-652010
- NIST-800-53-AU-4(1)
- NIST-800-53-CM-6(a)
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_rsyslog_enabled
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include enable_rsyslog
class enable_rsyslog {
service {'rsyslog':
enable => true,
ensure => 'running',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
SYSTEMCTL_EXEC='/usr/bin/systemctl'
"$SYSTEMCTL_EXEC" unmask 'rsyslog.service'
if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
"$SYSTEMCTL_EXEC" start 'rsyslog.service'
fi
"$SYSTEMCTL_EXEC" enable 'rsyslog.service'
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[customizations.services]
enabled = ["rsyslog"]
|
Ensure real-time clock is set to UTC
[ref]ruleEnsure that the system real-time clock (RTC) is set to Coordinated Universal Time (UTC). Rationale:If time stamps are not consistently applied and there is no common
time reference, it is difficult to perform forensic analysis.
Time stamps generated by the operating system include date and time.
Time is commonly expressed in UTC, a modern continuation of GMT, or
local time with an offset from UTC. Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
if timedatectl status | grep -i "time zone" | grep -iv 'UTC\|GMT'; then
timedatectl set-timezone UTC
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Network Configuration and Firewalls
[ref]groupMost systems must be connected to a network of some
sort, and this brings with it the substantial risk of network
attack. This section discusses the security impact of decisions
about networking which must be made when configuring a system.
This section also discusses firewalls, network access
controls, and other network security frameworks, which allow
system-level rules to be written that can limit an attackers' ability
to connect to your system. These rules can specify that network
traffic should be allowed or denied from certain IP addresses,
hosts, and networks. The rules can also specify which of the
system's network services are available to particular hosts or
networks. |
contains 8 rules |
Kernel Parameters Which Affect Networking
[ref]groupThe sysctl utility is used to set
parameters which affect the operation of the Linux kernel. Kernel parameters
which affect networking and have security implications are described here. |
contains 1 rule |
Network Related Kernel Runtime Parameters for Hosts and Routers
[ref]groupCertain kernel parameters should be set for systems which are
acting as either hosts or routers to improve the system's ability defend
against certain types of IPv4 protocol attacks. |
contains 1 rule |
Enable Kernel Parameter to Use TCP Syncookies on Network Interfaces
[ref]ruleTo set the runtime status of the net.ipv4.tcp_syncookies kernel parameter, run the following command: $ sudo sysctl -w net.ipv4.tcp_syncookies=1
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d : net.ipv4.tcp_syncookies = 1 Rationale:A TCP SYN flood attack can cause a denial of service by filling a
system's TCP connection table with connections in the SYN_RCVD state.
Syncookies can be used to track a connection when a subsequent ACK is received,
verifying the initiator is attempting a valid connection and is not a flood
source. This feature is activated when a flood condition is detected, and
enables the system to continue servicing valid connection requests. References:
R12, 3.3.8, 1, 12, 13, 14, 15, 16, 18, 2, 4, 6, 7, 8, 9, 5.10.1.1, APO01.06, APO13.01, BAI04.04, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.07, DSS06.02, 3.1.20, CCI-001095, CCI-000366, CCI-002385, 4.2.3.4, 4.3.3.4, 4.4.3.3, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.2, SR 7.1, SR 7.2, SR 7.6, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.1.3, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.17.2.1, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CM-7(a), CM-7(b), SC-5(1), SC-5(2), SC-5(3)(a), CM-6(a), DE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.PT-4, Req-1.4.1, 1.4.3, 1.4, SRG-OS-000480-GPOS-00227, SRG-OS-000420-GPOS-00186, SRG-OS-000142-GPOS-00071, UBTU-22-253010, SV-260522r958528_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.10.1.1
- DISA-STIG-UBTU-22-253010
- NIST-800-171-3.1.20
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-SC-5(1)
- NIST-800-53-SC-5(2)
- NIST-800-53-SC-5(3)(a)
- PCI-DSS-Req-1.4.1
- PCI-DSSv4-1.4
- PCI-DSSv4-1.4.3
- disable_strategy
- low_complexity
- medium_disruption
- medium_severity
- reboot_required
- sysctl_net_ipv4_tcp_syncookies
- name: List /etc/sysctl.d/*.conf files
find:
paths:
- /etc/sysctl.d/
- /run/sysctl.d/
- /usr/local/lib/sysctl.d/
contains: ^[\s]*net.ipv4.tcp_syncookies.*$
patterns: '*.conf'
file_type: any
register: find_sysctl_d
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.10.1.1
- DISA-STIG-UBTU-22-253010
- NIST-800-171-3.1.20
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-SC-5(1)
- NIST-800-53-SC-5(2)
- NIST-800-53-SC-5(3)(a)
- PCI-DSS-Req-1.4.1
- PCI-DSSv4-1.4
- PCI-DSSv4-1.4.3
- disable_strategy
- low_complexity
- medium_disruption
- medium_severity
- reboot_required
- sysctl_net_ipv4_tcp_syncookies
- name: Comment out any occurrences of net.ipv4.tcp_syncookies from config files
replace:
path: '{{ item.path }}'
regexp: ^[\s]*net.ipv4.tcp_syncookies
replace: '#net.ipv4.tcp_syncookies'
loop: '{{ find_sysctl_d.files }}'
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.10.1.1
- DISA-STIG-UBTU-22-253010
- NIST-800-171-3.1.20
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-SC-5(1)
- NIST-800-53-SC-5(2)
- NIST-800-53-SC-5(3)(a)
- PCI-DSS-Req-1.4.1
- PCI-DSSv4-1.4
- PCI-DSSv4-1.4.3
- disable_strategy
- low_complexity
- medium_disruption
- medium_severity
- reboot_required
- sysctl_net_ipv4_tcp_syncookies
- name: XCCDF Value sysctl_net_ipv4_tcp_syncookies_value # promote to variable
set_fact:
sysctl_net_ipv4_tcp_syncookies_value: !!str 1
tags:
- always
- name: Ensure sysctl net.ipv4.tcp_syncookies is set
sysctl:
name: net.ipv4.tcp_syncookies
value: '{{ sysctl_net_ipv4_tcp_syncookies_value }}'
sysctl_file: /etc/sysctl.conf
state: present
reload: true
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.10.1.1
- DISA-STIG-UBTU-22-253010
- NIST-800-171-3.1.20
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-SC-5(1)
- NIST-800-53-SC-5(2)
- NIST-800-53-SC-5(3)(a)
- PCI-DSS-Req-1.4.1
- PCI-DSSv4-1.4
- PCI-DSSv4-1.4.3
- disable_strategy
- low_complexity
- medium_disruption
- medium_severity
- reboot_required
- sysctl_net_ipv4_tcp_syncookies
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
# Comment out any occurrences of net.ipv4.tcp_syncookies from /etc/sysctl.d/*.conf files
for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do
# skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi
matching_list=$(grep -P '^(?!#).*[\s]*net.ipv4.tcp_syncookies.*$' $f | uniq )
if ! test -z "$matching_list"; then
while IFS= read -r entry; do
escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
# comment out "net.ipv4.tcp_syncookies" matches to preserve user data
sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
done <<< "$matching_list"
fi
done
#
# Set sysctl config file which to save the desired value
#
SYSCONFIG_FILE="/etc/sysctl.conf"
sysctl_net_ipv4_tcp_syncookies_value='1'
#
# Set runtime for net.ipv4.tcp_syncookies
#
if [[ "$OSCAP_BOOTC_BUILD" != "YES" ]] ; then
/sbin/sysctl -q -n -w net.ipv4.tcp_syncookies="$sysctl_net_ipv4_tcp_syncookies_value"
fi
#
# If net.ipv4.tcp_syncookies present in /etc/sysctl.conf, change value to appropriate value
# else, add "net.ipv4.tcp_syncookies = value" to /etc/sysctl.conf
#
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^net.ipv4.tcp_syncookies")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_tcp_syncookies_value"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^net.ipv4.tcp_syncookies\\>" "${SYSCONFIG_FILE}"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.tcp_syncookies\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
fi
printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Uncomplicated Firewall (ufw)
[ref]groupThe Linux kernel in Ubuntu provides a packet filtering system called
netfilter, and the traditional interface for manipulating netfilter are
the iptables suite of commands. iptables provide a complete firewall
solution that is both highly configurable and highly flexible.
Becoming proficient in iptables takes time, and getting started with
netfilter firewalling using only iptables can be a daunting task. As a
result, many frontends for iptables have been created over the years,
each trying to achieve a different result and targeting a different
audience.
The Uncomplicated Firewall (ufw) is a frontend for iptables and is
particularly well-suited for host-based firewalls. ufw provides a
framework for managing netfilter, as well as a command-line interface
for manipulating the firewall. ufw aims to provide an easy to use
interface for people unfamiliar with firewall concepts, while at the
same time simplifies complicated iptables commands to help an
administrator who knows what he or she is doing. ufw is an upstream
for other distributions and graphical frontends. |
contains 5 rules |
Install ufw Package
[ref]ruleThe ufw package can be installed with the following command:
$ apt-get install ufw Rationale:ufw controls the Linux kernel network packet filtering
code. ufw allows system operators to set up firewalls and IP
masquerading, etc. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-251010
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_ufw_installed
- name: Ensure ufw is installed
package:
name: ufw
state: present
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-251010
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_ufw_installed
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_ufw
class install_ufw {
package { 'ufw':
ensure => 'installed',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "ufw"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[[packages]]
name = "ufw"
version = "*"
|
Verify ufw Enabled
[ref]rule
The ufw service can be enabled with the following command:
$ sudo systemctl enable ufw.service Rationale:The ufw service must be enabled and running in order for ufw to protect the system Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-251020
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_ufw_enabled
- name: Verify ufw Enabled - Enable service ufw
block:
- name: Gather the package facts
package_facts:
manager: auto
- name: Verify ufw Enabled - Enable Service ufw
ansible.builtin.systemd:
name: ufw
enabled: true
state: started
masked: false
when:
- '"ufw" in ansible_facts.packages'
when:
- '"linux-base" in ansible_facts.packages'
- ( "ufw" in ansible_facts.packages and "linux-base" in ansible_facts.packages )
tags:
- DISA-STIG-UBTU-22-251020
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_ufw_enabled
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include enable_ufw
class enable_ufw {
service {'ufw':
enable => true,
ensure => 'running',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && { ( dpkg-query --show --showformat='${db:Status-Status}\n' 'ufw' 2>/dev/null | grep -q '^installed' && dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed ); }; then
SYSTEMCTL_EXEC='/usr/bin/systemctl'
"$SYSTEMCTL_EXEC" unmask 'ufw.service'
if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
"$SYSTEMCTL_EXEC" start 'ufw.service'
fi
"$SYSTEMCTL_EXEC" enable 'ufw.service'
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[customizations.services]
enabled = ["ufw"]
|
Verify ufw Active
[ref]ruleVerify the ufw is enabled on the system with the following command:
# sudo ufw status
If the above command returns the status as "inactive" or any type of error, this is a finding.Rationale:Remote access services, such as those providing remote access to network devices and information systems,
which lack automated control capabilities, increase risk and make remote user access management difficult at best.
Remote access is access to DOD nonpublic information systems by an authorized user (or an information system) communicating
through an external, nonorganization-controlled network. Remote access methods include, for example, dial-up, broadband, and wireless.
Ubuntu 22.04 LTS functionality (e.g., RDP) must be capable of taking enforcement action if the audit reveals unauthorized activity.
Automated control of remote access sessions allows organizations to ensure ongoing compliance with remote access policies by
enforcing connection rules of remote access applications on a variety of information system components. |
Only Allow Authorized Network Services in ufw
[ref]ruleCheck the firewall configuration for any unnecessary or prohibited
functions, ports, protocols, and/or services by running the following
command:
$ sudo ufw show raw
Chain OUTPUT (policy ACCEPT)
target prot opt sources destination
Chain INPUT (policy ACCEPT 1 packets, 40 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Ask the System Administrator for the site or program PPSM CLSA. Verify
the services allowed by the firewall match the PPSM CLSA.Rationale:To prevent unauthorized connection of devices, unauthorized transfer of
information, or unauthorized tunneling (i.e., embedding of data types
within data types), organizations must disable or restrict unused or
unnecessary physical and logical ports/protocols on information systems.
Operating systems are capable of providing a wide variety of functions
and services. Some of the functions and services provided by default
may not be necessary to support essential organizational operations.
Additionally, it is sometimes convenient to provide multiple services
from a single component (e.g., VPN and IPS); however, doing so
increases risk over limiting the services provided by any one component.
To support the requirements and principles of least functionality, the
operating system must support the organizational requirements, providing
only essential capabilities and limiting the use of ports, protocols,
and/or services to only those required, authorized, and approved to
conduct official business or to address authorized quality of life
issues. |
ufw Must rate-limit network interfaces
[ref]ruleThe operating system must configure the uncomplicated firewall to
rate-limit impacted network interfaces.
Check all the services listening to the ports with the following
command:
$ sudo ss -l46ut
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
tcp LISTEN 0 128 [::]:ssh [::]:*
For each entry, verify that the ufw is configured to rate limit the
service ports with the following command:
$ sudo ufw status
If any port with a state of "LISTEN" is not marked with the "LIMIT"
action, run the following command, replacing "service" with the
service that needs to be rate limited:
$ sudo ufw limit "service"
Rate-limiting can also be done on an interface. An example of adding
a rate-limit on the eth0 interface follows:
$ sudo ufw limit in on eth0 Rationale:This requirement addresses the configuration of the operating system to
mitigate the impact of DoS attacks that have occurred or are ongoing on
system availability. For each system, known and potential DoS attacks
must be identified and solutions for each type implemented. A variety
of technologies exist to limit or, in some cases, eliminate the effects
of DoS attacks (e.g., limiting processes or establishing memory
partitions). Employing increased capacity and bandwidth, combined with
service redundancy, may reduce the susceptibility to some DoS attacks. |
Wireless Networking
[ref]groupWireless networking, such as 802.11
(WiFi) and Bluetooth, can present a security risk to sensitive or
classified systems and networks. Wireless networking hardware is
much more likely to be included in laptop or portable systems than
in desktops or servers.
Removal of hardware provides the greatest assurance that the wireless
capability remains disabled. Acquisition policies often include provisions to
prevent the purchase of equipment that will be used in sensitive spaces and
includes wireless capabilities. If it is impractical to remove the wireless
hardware, and policy permits the device to enter sensitive spaces as long
as wireless is disabled, efforts should instead focus on disabling wireless capability
via software. |
contains 1 rule |
Disable Wireless Through Software Configuration
[ref]groupIf it is impossible to remove the wireless hardware
from the device in question, disable as much of it as possible
through software. The following methods can disable software
support for wireless networking, but note that these methods do not
prevent malicious software or careless users from re-activating the
devices. |
contains 1 rule |
Deactivate Wireless Network Interfaces
[ref]ruleDeactivating wireless network interfaces should prevent normal usage of the wireless
capability.
Verify that there are no wireless interfaces configured on the system
with the following command:
$ ls -L -d /sys/class/net/*/wireless | xargs dirname | xargs basename -a Rationale:The use of wireless networking can introduce many different attack vectors into
the organization's network. Common attack vectors such as malicious association
and ad hoc networks will allow an attacker to spoof a wireless access point
(AP), allowing validated systems to connect to the malicious AP and enabling the
attacker to monitor and record network traffic. These malicious APs can also
serve to create a man-in-the-middle attack or be used to create a denial of
service to valid network resources. References:
3.1.2, 11, 12, 14, 15, 3, 8, 9, APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06, 3.1.16, CCI-001443, CCI-001444, CCI-002421, CCI-002418, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6, 1315, 1319, A.11.2.6, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.2.1, A.6.2.2, A.9.1.2, AC-18(a), AC-18(3), CM-7(a), CM-7(b), CM-6(a), MP-7, PR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4, Req-1.3.3, 1.3.3, 1.3, SRG-OS-000299-GPOS-00117, SRG-OS-000300-GPOS-00118, SRG-OS-000424-GPOS-00188, SRG-OS-000481-GPOS-000481, UBTU-22-291015, SV-260541r958358_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | false |
---|
Strategy: | unknown |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-291015
- NIST-800-171-3.1.16
- NIST-800-53-AC-18(3)
- NIST-800-53-AC-18(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-MP-7
- PCI-DSS-Req-1.3.3
- PCI-DSSv4-1.3
- PCI-DSSv4-1.3.3
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- unknown_strategy
- wireless_disable_interfaces
- name: Service facts
ansible.builtin.service_facts: null
tags:
- DISA-STIG-UBTU-22-291015
- NIST-800-171-3.1.16
- NIST-800-53-AC-18(3)
- NIST-800-53-AC-18(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-MP-7
- PCI-DSS-Req-1.3.3
- PCI-DSSv4-1.3
- PCI-DSSv4-1.3.3
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- unknown_strategy
- wireless_disable_interfaces
- name: Ensure NetworkManager is installed
ansible.builtin.package:
name: '{{ item }}'
state: present
with_items:
- NetworkManager
tags:
- DISA-STIG-UBTU-22-291015
- NIST-800-171-3.1.16
- NIST-800-53-AC-18(3)
- NIST-800-53-AC-18(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-MP-7
- PCI-DSS-Req-1.3.3
- PCI-DSSv4-1.3
- PCI-DSSv4-1.3.3
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- unknown_strategy
- wireless_disable_interfaces
- name: NetworkManager Deactivate Wireless Network Interfaces
command: nmcli radio wifi off
when:
- '''NetworkManager'' in ansible_facts.packages'
- ansible_facts.services['NetworkManager.service'].state == 'running'
tags:
- DISA-STIG-UBTU-22-291015
- NIST-800-171-3.1.16
- NIST-800-53-AC-18(3)
- NIST-800-53-AC-18(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-MP-7
- PCI-DSS-Req-1.3.3
- PCI-DSSv4-1.3
- PCI-DSSv4-1.3.3
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- unknown_strategy
- wireless_disable_interfaces
Remediation Shell script: (show)
if [ -n "$(find /sys/class/net/*/ -type d -name wireless)" ]; then
interfaces=$(find /sys/class/net/*/wireless -type d -name wireless | xargs -0 dirname | xargs basename)
for i in $interfaces; do
ip link set dev "$i" down
drivers=$(basename "$(readlink -f /sys/class/net/"$i"/device/driver)")
echo "install $drivers /bin/false" >> /etc/modprobe.d/disable_wireless.conf
modprobe -r "$drivers"
done
fi
|
Transport Layer Security Support
[ref]groupSupport for Transport Layer Security (TLS), and its predecessor, the Secure
Sockets Layer (SSL), is included in Red Hat Enterprise Linux in the OpenSSL software (RPM package
openssl ). TLS provides encrypted and authenticated network
communications, and many network services include support for it. TLS or SSL
can be leveraged to avoid any plaintext transmission of sensitive data.
For information on how to use OpenSSL, see
http://www.openssl.org/docs/. Information on FIPS validation
of OpenSSL is available at http://www.openssl.org/docs/fips.html
and http://csrc.nist.gov/groups/STM/cmvp/documents/140-1/140val-all.htm. |
contains 1 rule |
Only Allow DoD PKI-established CAs
[ref]ruleThe operating system must only allow the use of DoD PKI-established
certificate authorities for verification of the establishment of
protected sessions. Rationale:Untrusted Certificate Authorities (CA) can issue certificates, but they
may be issued by organizations or individuals that seek to compromise
DoD systems or by organizations with insufficient security controls. If
the CA used for verifying the certificate is not a DoD-approved CA,
trust of this CA has not been established.
The DoD will only accept PKI-certificates obtained from a DoD-approved
internal or external certificate authority. Reliance on CAs for the
establishment of secure sessions includes, for example, the use of
SSL/TLS certificates. |
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 Ubuntu 22.04
installations:
$ mount -t xfs | awk '{print $3}'
For any systems that use a different
local filesystem type, modify this command as appropriate. |
contains 23 rules |
Verify Permissions on Important Files and
Directories
[ref]groupPermissions for many files on a system must be set
restrictively to ensure sensitive information is properly protected.
This section discusses important
permission restrictions which can be verified
to ensure that no harmful discrepancies have
arisen. |
contains 19 rules |
Verify Permissions on Files within /var/log Directory
[ref]groupThe /var/log directory contains files with logs of error
messages in the system and should only be accessed by authorized
personnel. |
contains 6 rules |
Verify Group Who Owns /var/log Directory
[ref]rule To properly set the group owner of /var/log , run the command: $ sudo chgrp syslog /var/log Rationale:The /var/log directory contains files with logs of error
messages in the system and should only be accessed by authorized
personnel. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Ensure group owner on /var/log/
file:
path: /var/log/
state: directory
group: syslog
tags:
- DISA-STIG-UBTU-22-232125
- configure_strategy
- file_groupowner_var_log
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find -H /var/log/ -maxdepth 1 -type d -exec chgrp -L syslog {} \;
|
Verify Group Who Owns /var/log/syslog File
[ref]rule To properly set the group owner of /var/log/syslog , run the command: $ sudo chgrp adm /var/log/syslog Rationale:The /var/log/syslog file contains logs of error messages in
the system and should only be accessed by authorized personnel. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Test for existence /var/log/syslog
stat:
path: /var/log/syslog
register: file_exists
tags:
- DISA-STIG-UBTU-22-232135
- configure_strategy
- file_groupowner_var_log_syslog
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner 4 on /var/log/syslog
file:
path: /var/log/syslog
group: '4'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232135
- configure_strategy
- file_groupowner_var_log_syslog
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
chgrp 4 /var/log/syslog
|
Verify User Who Owns /var/log Directory
[ref]rule To properly set the owner of /var/log , run the command: $ sudo chown root /var/log Rationale:The /var/log directory contains files with logs of error
messages in the system and should only be accessed by authorized
personnel. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Ensure owner on directory /var/log/
file:
path: /var/log/
state: directory
owner: '0'
tags:
- DISA-STIG-UBTU-22-232120
- configure_strategy
- file_owner_var_log
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find -H /var/log/ -maxdepth 1 -type d -exec chown -L 0 {} \;
|
Verify User Who Owns /var/log/syslog File
[ref]rule To properly set the owner of /var/log/syslog , run the command: $ sudo chown syslog /var/log/syslog Rationale:The /var/log/syslog file contains logs of error messages in
the system and should only be accessed by authorized personnel. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Test for existence /var/log/syslog
stat:
path: /var/log/syslog
register: file_exists
tags:
- DISA-STIG-UBTU-22-232130
- configure_strategy
- file_owner_var_log_syslog
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner 104 on /var/log/syslog
file:
path: /var/log/syslog
owner: '104'
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232130
- configure_strategy
- file_owner_var_log_syslog
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
chown 104 /var/log/syslog
|
Verify Permissions on /var/log Directory
[ref]rule
To properly set the permissions of /var/log , run the command:
$ sudo chmod 0755 /var/log Rationale:The /var/log directory contains files with logs of error
messages in the system and should only be accessed by authorized
personnel. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Find /var/log/ file(s)
command: 'find -H /var/log/ -maxdepth 1 -perm /u+s,g+ws,o+wt -type d '
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232025
- configure_strategy
- file_permissions_var_log
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /var/log/ file(s)
file:
path: '{{ item }}'
mode: u-s,g-ws,o-wt
state: directory
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232025
- configure_strategy
- file_permissions_var_log
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
chmod 0755 /var/log/
if grep -q "^z \/var\/log " /usr/lib/tmpfiles.d/00rsyslog.conf; then
sed -i --follow-symlinks "s/\(^z[[:space:]]\+\/var\/log[[:space:]]\+\)\(\([[:digit:]]\+\)[^ $]*\)/\10755/" /usr/lib/tmpfiles.d/00rsyslog.conf
fi
|
Verify Permissions on /var/log/syslog File
[ref]rule
To properly set the permissions of /var/log/syslog , run the command:
$ sudo chmod 0640 /var/log/syslog Rationale:The /var/log/syslog file contains logs of error messages in
the system and should only be accessed by authorized personnel. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Test for existence /var/log/syslog
stat:
path: /var/log/syslog
register: file_exists
tags:
- DISA-STIG-UBTU-22-232030
- configure_strategy
- file_permissions_var_log_syslog
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-xs,g-xws,o-xwrt on /var/log/syslog
file:
path: /var/log/syslog
mode: u-xs,g-xws,o-xwrt
when: file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232030
- configure_strategy
- file_permissions_var_log_syslog
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
chmod u-xs,g-xws,o-xwrt /var/log/syslog
|
Verify File Permissions Within Some Important Directories
[ref]groupSome directories contain files whose confidentiality or integrity
is notably important and may also be susceptible to misconfiguration over time, particularly if
unpackaged software is installed. As such,
an argument exists to verify that files' permissions within these directories remain
configured correctly and restrictively. |
contains 11 rules |
Verify that Shared Library Directories Have Root Group Ownership
[ref]ruleSystem-wide shared library files, which are linked to executables
during process load time or run time, are stored in the following directories
by default:
/lib
/lib64
/usr/lib
/usr/lib64
Kernel modules, which can be added to the kernel during runtime, are also
stored in /lib/modules . All files in these directories should be
group-owned by the root user. If the directories, is found to be owned
by a user other than root correct its
ownership with the following command:
$ sudo chgrp root DIR Rationale:Files from shared library directories are loaded into the address
space of processes (including privileged ones) or of the kernel itself at
runtime. Proper ownership of library directories is necessary to protect
the integrity of the system. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Ensure group owner on /lib/ recursively
file:
path: /lib/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-22-232065
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_group_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /lib64/ recursively
file:
path: /lib64/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-22-232065
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_group_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /usr/lib/ recursively
file:
path: /usr/lib/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-22-232065
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_group_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /usr/lib64/ recursively
file:
path: /usr/lib64/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-22-232065
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_group_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find -H /lib/ -type d -exec chgrp -L 0 {} \;
find -H /lib64/ -type d -exec chgrp -L 0 {} \;
find -H /usr/lib/ -type d -exec chgrp -L 0 {} \;
find -H /usr/lib64/ -type d -exec chgrp -L 0 {} \;
|
Verify that system commands directories are group owned by root
[ref]ruleSystem commands files are stored in the following directories by default:
/bin
/sbin
/usr/bin
/usr/sbin
/usr/local/bin
/usr/local/sbin
All these directories should be owned by the root group.
If the directory is found to be owned by a group other than root correct
its ownership with the following command:
$ sudo chgrp root DIR Rationale:If the operating system allows any user to make changes to software
libraries, then those changes might be implemented without undergoing the
appropriate testing and approvals that are part of a robust change management
process.
This requirement applies to operating systems with software libraries
that are accessible and configurable, as in the case of interpreted languages.
Software libraries also include privileged programs which execute with
escalated privileges. Only qualified and authorized individuals must be
allowed to obtain access to information system components for purposes
of initiating changes, including upgrades and modifications. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Ensure group owner on /bin/ recursively
file:
path: /bin/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-22-232045
- configure_strategy
- dir_groupownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /sbin/ recursively
file:
path: /sbin/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-22-232045
- configure_strategy
- dir_groupownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /usr/bin/ recursively
file:
path: /usr/bin/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-22-232045
- configure_strategy
- dir_groupownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /usr/sbin/ recursively
file:
path: /usr/sbin/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-22-232045
- configure_strategy
- dir_groupownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /usr/local/bin/ recursively
file:
path: /usr/local/bin/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-22-232045
- configure_strategy
- dir_groupownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /usr/local/sbin/ recursively
file:
path: /usr/local/sbin/
state: directory
recurse: true
group: '0'
tags:
- DISA-STIG-UBTU-22-232045
- configure_strategy
- dir_groupownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find -H /bin/ -type d -exec chgrp -L 0 {} \;
find -H /sbin/ -type d -exec chgrp -L 0 {} \;
find -H /usr/bin/ -type d -exec chgrp -L 0 {} \;
find -H /usr/sbin/ -type d -exec chgrp -L 0 {} \;
find -H /usr/local/bin/ -type d -exec chgrp -L 0 {} \;
find -H /usr/local/sbin/ -type d -exec chgrp -L 0 {} \;
|
Verify that System Executable Have Root Ownership
[ref]rule/bin
/sbin
/usr/bin
/usr/sbin
/usr/local/bin
/usr/local/sbin
All these directories should be owned by the root user.
If any directory DIR in these directories is found
to be owned by a user other than root, correct its ownership with the
following command:
$ sudo chown root DIR Rationale:System binaries are executed by privileged users as well as system services,
and restrictive permissions are necessary to ensure that their
execution of these programs cannot be co-opted. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Ensure owner on directory /bin/ recursively
file:
path: /bin/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-22-232040
- configure_strategy
- dir_ownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /sbin/ recursively
file:
path: /sbin/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-22-232040
- configure_strategy
- dir_ownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /usr/bin/ recursively
file:
path: /usr/bin/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-22-232040
- configure_strategy
- dir_ownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /usr/sbin/ recursively
file:
path: /usr/sbin/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-22-232040
- configure_strategy
- dir_ownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /usr/local/bin/ recursively
file:
path: /usr/local/bin/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-22-232040
- configure_strategy
- dir_ownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /usr/local/sbin/ recursively
file:
path: /usr/local/sbin/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-22-232040
- configure_strategy
- dir_ownership_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find -H /bin/ -type d -exec chown -L 0 {} \;
find -H /sbin/ -type d -exec chown -L 0 {} \;
find -H /usr/bin/ -type d -exec chown -L 0 {} \;
find -H /usr/sbin/ -type d -exec chown -L 0 {} \;
find -H /usr/local/bin/ -type d -exec chown -L 0 {} \;
find -H /usr/local/sbin/ -type d -exec chown -L 0 {} \;
|
Verify that Shared Library Directories Have Root Ownership
[ref]ruleSystem-wide shared library files, which are linked to executables
during process load time or run time, are stored in the following directories
by default:
/lib
/lib64
/usr/lib
/usr/lib64
Kernel modules, which can be added to the kernel during runtime, are also
stored in /lib/modules . All files in these directories should be
owned by the root user. If the directories, is found to be owned
by a user other than root correct its
ownership with the following command:
$ sudo chown root DIR Rationale:Files from shared library directories are loaded into the address
space of processes (including privileged ones) or of the kernel itself at
runtime. Proper ownership of library directories is necessary to protect
the integrity of the system. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Ensure owner on directory /lib/ recursively
file:
path: /lib/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-22-232060
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /lib64/ recursively
file:
path: /lib64/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-22-232060
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /usr/lib/ recursively
file:
path: /usr/lib/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-22-232060
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on directory /usr/lib64/ recursively
file:
path: /usr/lib64/
state: directory
recurse: true
owner: '0'
tags:
- DISA-STIG-UBTU-22-232060
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- dir_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find -H /lib/ -type d -exec chown -L 0 {} \;
find -H /lib64/ -type d -exec chown -L 0 {} \;
find -H /usr/lib/ -type d -exec chown -L 0 {} \;
find -H /usr/lib64/ -type d -exec chown -L 0 {} \;
|
Verify that System Executable Directories Have Restrictive Permissions
[ref]ruleSystem executables are stored in the following directories by default:
/bin
/sbin
/usr/bin
/usr/sbin
/usr/local/bin
/usr/local/sbin
These directories should not be group-writable or world-writable.
If any directory DIR in these directories is found to be
group-writable or world-writable, correct its permission with the
following command:
$ sudo chmod go-w DIR Rationale:System binaries are executed by privileged users, as well as system services,
and restrictive permissions are necessary to ensure execution of these programs
cannot be co-opted. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Find /bin/ file(s) recursively
command: 'find -H /bin/ -perm /u+s,g+ws,o+wt -type d '
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232010
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /bin/ file(s)
file:
path: '{{ item }}'
mode: u-s,g-ws,o-wt
state: directory
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232010
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /sbin/ file(s) recursively
command: 'find -H /sbin/ -perm /u+s,g+ws,o+wt -type d '
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232010
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /sbin/ file(s)
file:
path: '{{ item }}'
mode: u-s,g-ws,o-wt
state: directory
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232010
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /usr/bin/ file(s) recursively
command: 'find -H /usr/bin/ -perm /u+s,g+ws,o+wt -type d '
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232010
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /usr/bin/ file(s)
file:
path: '{{ item }}'
mode: u-s,g-ws,o-wt
state: directory
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232010
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /usr/sbin/ file(s) recursively
command: 'find -H /usr/sbin/ -perm /u+s,g+ws,o+wt -type d '
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232010
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /usr/sbin/ file(s)
file:
path: '{{ item }}'
mode: u-s,g-ws,o-wt
state: directory
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232010
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /usr/local/bin/ file(s) recursively
command: 'find -H /usr/local/bin/ -perm /u+s,g+ws,o+wt -type d '
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232010
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /usr/local/bin/ file(s)
file:
path: '{{ item }}'
mode: u-s,g-ws,o-wt
state: directory
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232010
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /usr/local/sbin/ file(s) recursively
command: 'find -H /usr/local/sbin/ -perm /u+s,g+ws,o+wt -type d '
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232010
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /usr/local/sbin/ file(s)
file:
path: '{{ item }}'
mode: u-s,g-ws,o-wt
state: directory
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232010
- configure_strategy
- dir_permissions_binary_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find -H /bin/ -perm /u+s,g+ws,o+wt -type d -exec chmod u-s,g-ws,o-wt {} \;
find -H /sbin/ -perm /u+s,g+ws,o+wt -type d -exec chmod u-s,g-ws,o-wt {} \;
find -H /usr/bin/ -perm /u+s,g+ws,o+wt -type d -exec chmod u-s,g-ws,o-wt {} \;
find -H /usr/sbin/ -perm /u+s,g+ws,o+wt -type d -exec chmod u-s,g-ws,o-wt {} \;
find -H /usr/local/bin/ -perm /u+s,g+ws,o+wt -type d -exec chmod u-s,g-ws,o-wt {} \;
find -H /usr/local/sbin/ -perm /u+s,g+ws,o+wt -type d -exec chmod u-s,g-ws,o-wt {} \;
|
Verify that system commands files are group owned by root or a system account
[ref]ruleSystem commands files are stored in the following directories by default:
/bin
/sbin
/usr/bin
/usr/sbin
/usr/local/bin
/usr/local/sbin
All files in these directories should be owned by the root group,
or a system account.
If the directory, or any file in these directories, is found to be owned
by a group other than root or a a system account correct its ownership
with the following command:
$ sudo chgrp root FILE Rationale:If the operating system allows any user to make changes to software
libraries, then those changes might be implemented without undergoing the
appropriate testing and approvals that are part of a robust change management
process.
This requirement applies to operating systems with software libraries
that are accessible and configurable, as in the case of interpreted languages.
Software libraries also include privileged programs which execute with
escalated privileges. Only qualified and authorized individuals must be
allowed to obtain access to information system components for purposes
of initiating changes, including upgrades and modifications. Remediation Shell script: (show)
for SYSCMDFILES in /bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin
do
find -L $SYSCMDFILES ! -group root -type f ! -perm /2000 -exec chgrp root '{}' \;
done
|
Verify that System Executables Have Root Ownership
[ref]ruleSystem executables are stored in the following directories by default:
/bin
/sbin
/usr/bin
/usr/libexec
/usr/local/bin
/usr/local/sbin
/usr/sbin
All files in these directories should be owned by the root user.
If any file FILE in these directories is found
to be owned by a user other than root, correct its ownership with the
following command:
$ sudo chown root FILE Rationale:System binaries are executed by privileged users as well as system services,
and restrictive permissions are necessary to ensure that their
execution of these programs cannot be co-opted. References:
R50, 12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, CCI-001499, 4.3.3.7.3, SR 2.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, CM-5(6), CM-5(6).1, CM-6(a), AC-6(1), PR.AC-4, PR.DS-5, SRG-OS-000259-GPOS-00100, UBTU-22-232050, SV-260495r991560_rule |
Verify that Shared Library Files Have Root Ownership
[ref]ruleSystem-wide shared library files, which are linked to executables
during process load time or run time, are stored in the following directories
by default:
/lib
/lib64
/usr/lib
/usr/lib64
Kernel modules, which can be added to the kernel during runtime, are also
stored in /lib/modules . All files in these directories should be
owned by the root user. If the directory, or any file in these
directories, is found to be owned by a user other than root correct its
ownership with the following command:
$ sudo chown root FILE Rationale:Files from shared library directories are loaded into the address
space of processes (including privileged ones) or of the kernel itself at
runtime. Proper ownership is necessary to protect the integrity of the system. References:
12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, CCI-001499, 4.3.3.7.3, SR 2.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, CM-5(6), CM-5(6).1, CM-6(a), AC-6(1), PR.AC-4, PR.DS-5, SRG-OS-000259-GPOS-00100, UBTU-22-232070, SV-260499r991560_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Find /lib/ file(s) matching ^.*$ recursively
command: find -H /lib/ -type f ! -uid 0 -regextype posix-extended -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232070
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on /lib/ file(s) matching ^.*$
file:
path: '{{ item }}'
owner: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232070
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /lib64/ file(s) matching ^.*$ recursively
command: find -H /lib64/ -type f ! -uid 0 -regextype posix-extended -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232070
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on /lib64/ file(s) matching ^.*$
file:
path: '{{ item }}'
owner: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232070
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /usr/lib/ file(s) matching ^.*$ recursively
command: find -H /usr/lib/ -type f ! -uid 0 -regextype posix-extended -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232070
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on /usr/lib/ file(s) matching ^.*$
file:
path: '{{ item }}'
owner: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232070
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /usr/lib64/ file(s) matching ^.*$ recursively
command: find -H /usr/lib64/ -type f ! -uid 0 -regextype posix-extended -regex
"^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232070
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on /usr/lib64/ file(s) matching ^.*$
file:
path: '{{ item }}'
owner: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232070
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_ownership_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find /lib/ -type f ! -uid 0 -regextype posix-extended -regex '^.*$' -exec chown -L 0 {} \;
find /lib64/ -type f ! -uid 0 -regextype posix-extended -regex '^.*$' -exec chown -L 0 {} \;
find /usr/lib/ -type f ! -uid 0 -regextype posix-extended -regex '^.*$' -exec chown -L 0 {} \;
find /usr/lib64/ -type f ! -uid 0 -regextype posix-extended -regex '^.*$' -exec chown -L 0 {} \;
|
Verify that System Executables Have Restrictive Permissions
[ref]ruleSystem executables are stored in the following directories by default:
/bin
/sbin
/usr/bin
/usr/libexec
/usr/local/bin
/usr/local/sbin
/usr/sbin
All files in these directories should not be group-writable or world-writable.
If any file FILE in these directories is found
to be group-writable or world-writable, correct its permission with the
following command:
$ sudo chmod go-w FILE Rationale:System binaries are executed by privileged users, as well as system services,
and restrictive permissions are necessary to ensure execution of these programs
cannot be co-opted. References:
R50, 12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, CCI-001499, 4.3.3.7.3, SR 2.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, CM-5(6), CM-5(6).1, CM-6(a), AC-6(1), PR.AC-4, PR.DS-5, SRG-OS-000259-GPOS-00100, UBTU-22-232015, SV-260486r991560_rule Remediation Shell script: (show)
DIRS="/bin /usr/bin /usr/local/bin /sbin /usr/sbin /usr/local/sbin /usr/libexec"
for dirPath in $DIRS; do
find "$dirPath" -perm /022 -exec chmod go-w '{}' \;
done
|
Verify that Shared Library Files Have Restrictive Permissions
[ref]ruleSystem-wide shared library files, which are linked to executables
during process load time or run time, are stored in the following directories
by default:
/lib
/lib64
/usr/lib
/usr/lib64
Kernel modules, which can be added to the kernel during runtime, are
stored in /lib/modules . All files in these directories
should not be group-writable or world-writable. If any file in these
directories is found to be group-writable or world-writable, correct
its permission with the following command:
$ sudo chmod go-w FILE Rationale:Files from shared library directories are loaded into the address
space of processes (including privileged ones) or of the kernel itself at
runtime. Restrictive permissions are necessary to protect the integrity of the system. References:
12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, CCI-001499, 4.3.3.7.3, SR 2.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, CM-6(a), CM-5(6), CM-5(6).1, AC-6(1), PR.AC-4, PR.DS-5, SRG-OS-000259-GPOS-00100, UBTU-22-232020, SV-260487r991560_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Find /lib/ file(s) recursively
command: find -H /lib/ -perm /g+w,o+w -type f -regextype posix-extended -regex
"^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232020
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /lib/ file(s)
file:
path: '{{ item }}'
mode: g-w,o-w
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232020
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /lib64/ file(s) recursively
command: find -H /lib64/ -perm /g+w,o+w -type f -regextype posix-extended -regex
"^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232020
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /lib64/ file(s)
file:
path: '{{ item }}'
mode: g-w,o-w
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232020
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /usr/lib/ file(s) recursively
command: find -H /usr/lib/ -perm /g+w,o+w -type f -regextype posix-extended -regex
"^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232020
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /usr/lib/ file(s)
file:
path: '{{ item }}'
mode: g-w,o-w
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232020
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /usr/lib64/ file(s) recursively
command: find -H /usr/lib64/ -perm /g+w,o+w -type f -regextype posix-extended
-regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232020
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /usr/lib64/ file(s)
file:
path: '{{ item }}'
mode: g-w,o-w
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232020
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- NIST-800-53-CM-6(a)
- configure_strategy
- file_permissions_library_dirs
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find /lib/ -perm /g+w,o+w -type f -regextype posix-extended -regex '^.*$' -exec chmod g-w,o-w {} \;
find /lib64/ -perm /g+w,o+w -type f -regextype posix-extended -regex '^.*$' -exec chmod g-w,o-w {} \;
find /usr/lib/ -perm /g+w,o+w -type f -regextype posix-extended -regex '^.*$' -exec chmod g-w,o-w {} \;
find /usr/lib64/ -perm /g+w,o+w -type f -regextype posix-extended -regex '^.*$' -exec chmod g-w,o-w {} \;
|
Verify the system-wide library files in directories
"/lib", "/lib64", "/usr/lib/" and "/usr/lib64" are group-owned by root.
[ref]ruleSystem-wide library files are stored in the following directories
by default:
/lib
/lib64
/usr/lib
/usr/lib64
All system-wide shared library files should be protected from unauthorised
access. If any of these files is not group-owned by root, correct its group-owner with
the following command:
$ sudo chgrp root FILE Rationale:If the operating system were to allow any user to make changes to software libraries,
then those changes might be implemented without undergoing the appropriate testing and
approvals that are part of a robust change management process.
This requirement applies to operating systems with software libraries that are
accessible and configurable, as in the case of interpreted languages. Software libraries
also include privileged programs which execute with escalated privileges. Only qualified
and authorized individuals must be allowed to obtain access to information system components
for purposes of initiating changes, including upgrades and modifications. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Find /lib/ file(s) matching ^.*$ recursively
command: find -H /lib/ -type f ! -group 0 -regextype posix-extended -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232075
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- root_permissions_syslibrary_files
- name: Ensure group owner on /lib/ file(s) matching ^.*$
file:
path: '{{ item }}'
group: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232075
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- root_permissions_syslibrary_files
- name: Find /lib64/ file(s) matching ^.*$ recursively
command: find -H /lib64/ -type f ! -group 0 -regextype posix-extended -regex "^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232075
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- root_permissions_syslibrary_files
- name: Ensure group owner on /lib64/ file(s) matching ^.*$
file:
path: '{{ item }}'
group: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232075
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- root_permissions_syslibrary_files
- name: Find /usr/lib/ file(s) matching ^.*$ recursively
command: find -H /usr/lib/ -type f ! -group 0 -regextype posix-extended -regex
"^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232075
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- root_permissions_syslibrary_files
- name: Ensure group owner on /usr/lib/ file(s) matching ^.*$
file:
path: '{{ item }}'
group: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232075
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- root_permissions_syslibrary_files
- name: Find /usr/lib64/ file(s) matching ^.*$ recursively
command: find -H /usr/lib64/ -type f ! -group 0 -regextype posix-extended -regex
"^.*$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232075
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- root_permissions_syslibrary_files
- name: Ensure group owner on /usr/lib64/ file(s) matching ^.*$
file:
path: '{{ item }}'
group: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232075
- NIST-800-53-CM-5(6)
- NIST-800-53-CM-5(6).1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- root_permissions_syslibrary_files
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find /lib/ -type f ! -group 0 -regextype posix-extended -regex '^.*$' -exec chgrp -L 0 {} \;
find /lib64/ -type f ! -group 0 -regextype posix-extended -regex '^.*$' -exec chgrp -L 0 {} \;
find /usr/lib/ -type f ! -group 0 -regextype posix-extended -regex '^.*$' -exec chgrp -L 0 {} \;
find /usr/lib64/ -type f ! -group 0 -regextype posix-extended -regex '^.*$' -exec chgrp -L 0 {} \;
|
Verify that All World-Writable Directories Have Sticky Bits Set
[ref]ruleWhen the so-called 'sticky bit' is set on a directory, only the owner of a given file may
remove that file from the directory. Without the sticky bit, any user with write access to a
directory may remove any file in the directory. Setting the sticky bit prevents users from
removing each other's files. In cases where there is no reason for a directory to be
world-writable, a better solution is to remove that permission rather than to set the sticky
bit. However, if a directory is used by a particular application, consult that application's
documentation instead of blindly changing modes.
To set the sticky bit on a world-writable directory DIR, run the following command:
$ sudo chmod +t DIR Warning:
This rule can take a long time to perform the check and might consume a considerable
amount of resources depending on the number of directories present on the system. It is
not a problem in most cases, but especially systems with a large number of directories can
be affected. See https://access.redhat.com/articles/6999111 . Rationale:Failing to set the sticky bit on public directories allows unauthorized users to delete files
in the directory structure.
The only authorized public directories are those temporary directories supplied with the
system, or those designed to be temporary file repositories. The setting is normally reserved
for directories used by the system, by users for temporary file storage (such as /tmp ),
and for directories requiring global read/write access. References:
R54, 1.1.22, 12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, CCI-001090, 4.3.3.7.3, SR 2.1, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, CM-6(a), AC-6(1), PR.AC-4, PR.DS-5, 2.2.6, 2.2, SRG-OS-000138-GPOS-00069, UBTU-22-232145, SV-260513r958524_rule Remediation Shell script: (show)
df --local -P | awk '{if (NR!=1) print $6}' \
| xargs -I '$6' find '$6' -xdev -type d \
\( -perm -0002 -a ! -perm -1000 \) 2>/dev/null \
-exec chmod a+t {} +
|
Verify permissions of log files
[ref]ruleAny operating system providing too much information in error messages
risks compromising the data and security of the structure, and content
of error messages needs to be carefully considered by the organization.
Organizations carefully consider the structure/content of error messages.
The extent to which information systems are able to identify and handle
error conditions is guided by organizational policy and operational
requirements. Information that could be exploited by adversaries includes,
for example, erroneous logon attempts with passwords entered by mistake
as the username, mission/business information that can be derived from
(if not stated explicitly by) information recorded, and personal
information, such as account numbers, social security numbers, and credit
card numbers. Rationale:The Ubuntu 22.04 must generate error messages that provide information
necessary for corrective actions without revealing information that could
be exploited by adversaries. References:
4.2.3, CCI-001312, SI-11(a), SI-11(b), SI-11.1(iii), PR.AC-4, PR.DS-5, 10.3.1, 10.3, SRG-OS-000205-GPOS-00083, UBTU-22-232026, SV-260489r958564_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Find /var/log/ file(s) recursively
command: find -H /var/log/ -perm /u+xs,g+xws,o+xwrt ! -name "history.log" ! -name
"eipp.log.xz" ! -name "*[bw]tmp" ! -name "*lastlog" -type f -regextype posix-extended
-regex ".*"
register: files_found
changed_when: false
failed_when: false
check_mode: false
tags:
- DISA-STIG-UBTU-22-232026
- NIST-800-53-SI-11(a)
- NIST-800-53-SI-11(b)
- NIST-800-53-SI-11.1(iii)
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- permissions_local_var_log
- name: Set permissions for /var/log/ file(s)
file:
path: '{{ item }}'
mode: u-xs,g-xws,o-xwrt
state: file
with_items:
- '{{ files_found.stdout_lines }}'
tags:
- DISA-STIG-UBTU-22-232026
- NIST-800-53-SI-11(a)
- NIST-800-53-SI-11(b)
- NIST-800-53-SI-11.1(iii)
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.1
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- permissions_local_var_log
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
find /var/log/ -perm /u+xs,g+xws,o+xwrt ! -name 'history.log' ! -name 'eipp.log.xz' ! -name '*[bw]tmp' ! -name '*lastlog' -type f -regextype posix-extended -regex '.*' -exec chmod u-xs,g-xws,o-xwrt {} \;
|
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 Modprobe Loading of USB Storage Driver
[ref]ruleTo prevent USB storage devices from being used, configure the kernel module loading system
to prevent automatic loading of the USB storage driver.
To configure the system to prevent the usb-storage
kernel module from being loaded, add the following line to the file /etc/modprobe.d/usb-storage.conf :
install usb-storage /bin/false
This will prevent the modprobe program from loading the usb-storage
module, but will not prevent an administrator (or another program) from using the
insmod program to load the module manually.Rationale:USB storage devices such as thumb drives can be used to introduce
malicious software. References:
1.1.10, 1, 12, 15, 16, 5, APO13.01, DSS01.04, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, 3.1.21, CCI-000778, CCI-001958, CCI-003959, 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.310(d)(1), 164.310(d)(2), 164.312(a)(1), 164.312(a)(2)(iv), 164.312(b), 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.6, A.11.2.6, A.13.1.1, A.13.2.1, A.18.1.4, A.6.2.1, A.6.2.2, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3, CM-7(a), CM-7(b), CM-6(a), MP-7, PR.AC-1, PR.AC-3, PR.AC-6, PR.AC-7, 3.4.2, 3.4, SRG-OS-000114-GPOS-00059, SRG-OS-000378-GPOS-00163, SRG-OS-000480-GPOS-00227, SRG-APP-000141-CTR-000315, UBTU-22-291010, SV-260540r986276_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-291010
- NIST-800-171-3.1.21
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-MP-7
- PCI-DSSv4-3.4
- PCI-DSSv4-3.4.2
- disable_strategy
- kernel_module_usb-storage_disabled
- low_complexity
- medium_disruption
- medium_severity
- reboot_required
- name: Ensure kernel module 'usb-storage' is disabled
lineinfile:
create: true
dest: /etc/modprobe.d/usb-storage.conf
regexp: install\s+usb-storage
line: install usb-storage /bin/false
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-291010
- NIST-800-171-3.1.21
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-MP-7
- PCI-DSSv4-3.4
- PCI-DSSv4-3.4.2
- disable_strategy
- kernel_module_usb-storage_disabled
- low_complexity
- medium_disruption
- medium_severity
- reboot_required
- name: Ensure kernel module 'usb-storage' is blacklisted
lineinfile:
create: true
dest: /etc/modprobe.d/usb-storage.conf
regexp: ^blacklist usb-storage$
line: blacklist usb-storage
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-291010
- NIST-800-171-3.1.21
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-MP-7
- PCI-DSSv4-3.4
- PCI-DSSv4-3.4.2
- disable_strategy
- kernel_module_usb-storage_disabled
- low_complexity
- medium_disruption
- medium_severity
- reboot_required
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
if LC_ALL=C grep -q -m 1 "^install usb-storage" /etc/modprobe.d/usb-storage.conf ; then
sed -i 's#^install usb-storage.*#install usb-storage /bin/false#g' /etc/modprobe.d/usb-storage.conf
else
echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/usb-storage.conf
echo "install usb-storage /bin/false" >> /etc/modprobe.d/usb-storage.conf
fi
if ! LC_ALL=C grep -q -m 1 "^blacklist usb-storage$" /etc/modprobe.d/usb-storage.conf ; then
echo "blacklist usb-storage" >> /etc/modprobe.d/usb-storage.conf
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Restrict Programs from Dangerous Execution Patterns
[ref]groupThe recommendations in this section are designed to
ensure that the system's features to protect against potentially
dangerous program execution are activated.
These protections are applied at the system initialization or
kernel level, and defend against certain types of badly-configured
or compromised programs. |
contains 3 rules |
Enable ExecShield
[ref]groupExecShield describes kernel features that provide
protection against exploitation of memory corruption errors such as buffer
overflows. These features include random placement of the stack and other
memory regions, prevention of execution in memory that should only hold data,
and special handling of text buffers. These protections are enabled by default
on 32-bit systems and controlled through sysctl variables
kernel.exec-shield and kernel.randomize_va_space . On the latest
64-bit systems, kernel.exec-shield cannot be enabled or disabled with
sysctl . |
contains 1 rule |
Enable Randomized Layout of Virtual Address Space
[ref]ruleTo set the runtime status of the kernel.randomize_va_space kernel parameter, run the following command: $ sudo sysctl -w kernel.randomize_va_space=2
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d : kernel.randomize_va_space = 2 Rationale:Address space layout randomization (ASLR) makes it more difficult for an
attacker to predict the location of attack code they have introduced into a
process's address space during an attempt at exploitation. Additionally,
ASLR makes it more difficult for an attacker to know the location of
existing code in order to re-purpose it using return oriented programming
(ROP) techniques. References:
R9, 1.5.1, 3.1.7, CCI-000366, CCI-002824, 164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e), CIP-002-5 R1.1, CIP-002-5 R1.2, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 4.1, CIP-004-6 4.2, CIP-004-6 R2.2.3, CIP-004-6 R2.2.4, CIP-004-6 R2.3, CIP-004-6 R4, CIP-005-6 R1, CIP-005-6 R1.1, CIP-005-6 R1.2, CIP-007-3 R3, CIP-007-3 R3.1, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, CIP-007-3 R8.4, CIP-009-6 R.1.1, CIP-009-6 R4, SC-30, SC-30(2), CM-6(a), Req-2.2.1, 3.3.1.1, 3.3.1, 3.3, SRG-OS-000433-GPOS-00193, SRG-OS-000480-GPOS-00227, SRG-APP-000450-CTR-001105, UBTU-22-213020, SV-260474r958928_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-213020
- NIST-800-171-3.1.7
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-30
- NIST-800-53-SC-30(2)
- PCI-DSS-Req-2.2.1
- PCI-DSSv4-3.3
- PCI-DSSv4-3.3.1
- PCI-DSSv4-3.3.1.1
- disable_strategy
- low_complexity
- medium_disruption
- medium_severity
- reboot_required
- sysctl_kernel_randomize_va_space
- name: List /etc/sysctl.d/*.conf files
find:
paths:
- /etc/sysctl.d/
- /run/sysctl.d/
- /usr/local/lib/sysctl.d/
contains: ^[\s]*kernel.randomize_va_space.*$
patterns: '*.conf'
file_type: any
register: find_sysctl_d
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-213020
- NIST-800-171-3.1.7
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-30
- NIST-800-53-SC-30(2)
- PCI-DSS-Req-2.2.1
- PCI-DSSv4-3.3
- PCI-DSSv4-3.3.1
- PCI-DSSv4-3.3.1.1
- disable_strategy
- low_complexity
- medium_disruption
- medium_severity
- reboot_required
- sysctl_kernel_randomize_va_space
- name: Comment out any occurrences of kernel.randomize_va_space from config files
replace:
path: '{{ item.path }}'
regexp: ^[\s]*kernel.randomize_va_space
replace: '#kernel.randomize_va_space'
loop: '{{ find_sysctl_d.files }}'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-213020
- NIST-800-171-3.1.7
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-30
- NIST-800-53-SC-30(2)
- PCI-DSS-Req-2.2.1
- PCI-DSSv4-3.3
- PCI-DSSv4-3.3.1
- PCI-DSSv4-3.3.1.1
- disable_strategy
- low_complexity
- medium_disruption
- medium_severity
- reboot_required
- sysctl_kernel_randomize_va_space
- name: Ensure sysctl kernel.randomize_va_space is set to 2
sysctl:
name: kernel.randomize_va_space
value: '2'
sysctl_file: /etc/sysctl.conf
state: present
reload: true
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-213020
- NIST-800-171-3.1.7
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-30
- NIST-800-53-SC-30(2)
- PCI-DSS-Req-2.2.1
- PCI-DSSv4-3.3
- PCI-DSSv4-3.3.1
- PCI-DSSv4-3.3.1.1
- disable_strategy
- low_complexity
- medium_disruption
- medium_severity
- reboot_required
- sysctl_kernel_randomize_va_space
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
# Comment out any occurrences of kernel.randomize_va_space from /etc/sysctl.d/*.conf files
for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do
# skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi
matching_list=$(grep -P '^(?!#).*[\s]*kernel.randomize_va_space.*$' $f | uniq )
if ! test -z "$matching_list"; then
while IFS= read -r entry; do
escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
# comment out "kernel.randomize_va_space" matches to preserve user data
sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
done <<< "$matching_list"
fi
done
#
# Set sysctl config file which to save the desired value
#
SYSCONFIG_FILE="/etc/sysctl.conf"
#
# Set runtime for kernel.randomize_va_space
#
if [[ "$OSCAP_BOOTC_BUILD" != "YES" ]] ; then
/sbin/sysctl -q -n -w kernel.randomize_va_space="2"
fi
#
# If kernel.randomize_va_space present in /etc/sysctl.conf, change value to "2"
# else, add "kernel.randomize_va_space = 2" to /etc/sysctl.conf
#
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^kernel.randomize_va_space")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "2"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^kernel.randomize_va_space\\>" "${SYSCONFIG_FILE}"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^kernel.randomize_va_space\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
fi
printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Enable Execute Disable (XD) or No Execute (NX) Support on
x86 Systems
[ref]groupRecent processors in the x86 family support the
ability to prevent code execution on a per memory page basis.
Generically and on AMD processors, this ability is called No
Execute (NX), while on Intel processors it is called Execute
Disable (XD). This ability can help prevent exploitation of buffer
overflow vulnerabilities and should be activated whenever possible.
Extra steps must be taken to ensure that this protection is
enabled, particularly on 32-bit x86 systems. Other processors, such
as Itanium and POWER, have included such support since inception
and the standard kernel for those platforms supports the
feature. This is enabled by default on the latest Oracle Linux, Red Hat and
Fedora systems if supported by the hardware. |
contains 1 rule |
Enable NX or XD Support in the BIOS
[ref]ruleReboot the system and enter the BIOS or Setup configuration menu.
Navigate the BIOS configuration menu and make sure that the option is enabled. The setting may be located
under a Security section. Look for Execute Disable (XD) on Intel-based systems and No Execute (NX)
on AMD-based systems. Rationale:Computers with the ability to prevent this type of code execution frequently put an option in the BIOS that will
allow users to turn the feature on or off at will. References:
11, 3, 9, BAI10.01, BAI10.02, BAI10.03, BAI10.05, 3.1.7, CCI-002824, 4.3.4.3.2, 4.3.4.3.3, SR 7.6, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, SC-39, CM-6(a), PR.IP-1, 2.2.1, 2.2, SRG-OS-000433-GPOS-00192, SRG-APP-000450-CTR-001105, UBTU-22-213025, SV-260475r958928_rule |
Restrict Access to Kernel Message Buffer
[ref]ruleTo set the runtime status of the kernel.dmesg_restrict kernel parameter, run the following command: $ sudo sysctl -w kernel.dmesg_restrict=1
To make sure that the setting is persistent, add the following line to a file in the directory /etc/sysctl.d : kernel.dmesg_restrict = 1 Rationale:Unprivileged access to the kernel syslog can expose sensitive kernel
address information. References:
R9, 3.1.5, CCI-001082, CCI-001090, 164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e), SI-11(a), SI-11(b), FMT_SMF_EXT.1, SRG-OS-000132-GPOS-00067, SRG-OS-000138-GPOS-00069, SRG-APP-000243-CTR-000600, UBTU-22-213010, SV-260472r958524_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-213010
- NIST-800-171-3.1.5
- NIST-800-53-SI-11(a)
- NIST-800-53-SI-11(b)
- disable_strategy
- low_complexity
- low_severity
- medium_disruption
- reboot_required
- sysctl_kernel_dmesg_restrict
- name: List /etc/sysctl.d/*.conf files
find:
paths:
- /etc/sysctl.d/
- /run/sysctl.d/
- /usr/local/lib/sysctl.d/
contains: ^[\s]*kernel.dmesg_restrict.*$
patterns: '*.conf'
file_type: any
register: find_sysctl_d
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-213010
- NIST-800-171-3.1.5
- NIST-800-53-SI-11(a)
- NIST-800-53-SI-11(b)
- disable_strategy
- low_complexity
- low_severity
- medium_disruption
- reboot_required
- sysctl_kernel_dmesg_restrict
- name: Comment out any occurrences of kernel.dmesg_restrict from config files
replace:
path: '{{ item.path }}'
regexp: ^[\s]*kernel.dmesg_restrict
replace: '#kernel.dmesg_restrict'
loop: '{{ find_sysctl_d.files }}'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-213010
- NIST-800-171-3.1.5
- NIST-800-53-SI-11(a)
- NIST-800-53-SI-11(b)
- disable_strategy
- low_complexity
- low_severity
- medium_disruption
- reboot_required
- sysctl_kernel_dmesg_restrict
- name: Ensure sysctl kernel.dmesg_restrict is set to 1
sysctl:
name: kernel.dmesg_restrict
value: '1'
sysctl_file: /etc/sysctl.conf
state: present
reload: true
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-213010
- NIST-800-171-3.1.5
- NIST-800-53-SI-11(a)
- NIST-800-53-SI-11(b)
- disable_strategy
- low_complexity
- low_severity
- medium_disruption
- reboot_required
- sysctl_kernel_dmesg_restrict
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
# Comment out any occurrences of kernel.dmesg_restrict from /etc/sysctl.d/*.conf files
for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do
# skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi
matching_list=$(grep -P '^(?!#).*[\s]*kernel.dmesg_restrict.*$' $f | uniq )
if ! test -z "$matching_list"; then
while IFS= read -r entry; do
escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
# comment out "kernel.dmesg_restrict" matches to preserve user data
sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
done <<< "$matching_list"
fi
done
#
# Set sysctl config file which to save the desired value
#
SYSCONFIG_FILE="/etc/sysctl.conf"
#
# Set runtime for kernel.dmesg_restrict
#
if [[ "$OSCAP_BOOTC_BUILD" != "YES" ]] ; then
/sbin/sysctl -q -n -w kernel.dmesg_restrict="1"
fi
#
# If kernel.dmesg_restrict present in /etc/sysctl.conf, change value to "1"
# else, add "kernel.dmesg_restrict = 1" to /etc/sysctl.conf
#
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^kernel.dmesg_restrict")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "1"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^kernel.dmesg_restrict\\>" "${SYSCONFIG_FILE}"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^kernel.dmesg_restrict\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
else
if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
fi
printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Services
[ref]groupThe best protection against vulnerable software is running less software. This section describes how to review
the software which Ubuntu 22.04 installs on a system and disable software which is not needed. It
then enumerates the software packages installed on a default Ubuntu 22.04 system and provides guidance about which
ones can be safely disabled.
Ubuntu 22.04 provides a convenient minimal install option that essentially installs the bare necessities for a functional
system. When building Ubuntu 22.04 systems, it is highly recommended to select the minimal packages and then build up
the system from there. |
contains 24 rules |
APT service configuration
[ref]groupThe apt service manage the package management and update of the whole system. Its configuration need to be properly defined to ensure efficient security updates, packages and repository authentication and proper lifecycle management. |
contains 1 rule |
Disable unauthenticated repositories in APT configuration
[ref]ruleUnauthenticated repositories should not be used for updates. Rationale:Repositories hosts all packages that will be intsalled on the system during update.
If a repository is not authenticated, the associated packages can't be trusted,
and then should not be installed localy. |
Base Services
[ref]groupThis section addresses the base services that are installed on a
Ubuntu 22.04 default installation which are not covered in other
sections. Some of these services listen on the network and
should be treated with particular discretion. Other services are local
system utilities that may or may not be extraneous. In general, system services
should be disabled if not required. |
contains 1 rule |
Disable KDump Kernel Crash Analyzer (kdump)
[ref]ruleThe kdump-tools service provides a kernel crash dump analyzer. It uses the kexec
system call to boot a secondary kernel ("capture" kernel) following a system
crash, which can load information from the crashed kernel for analysis.
The kdump-tools service can be disabled with the following command:
$ sudo systemctl mask --now kdump-tools.service Rationale:Kernel core dumps may contain the full contents of system memory at the
time of the crash. Kernel core dumps consume a considerable amount of disk
space and may result in denial of service by exhausting the available space
on the target file system partition. Unless the system is used for kernel
development or testing, there is little need to run the kdump service. References:
11, 12, 14, 15, 3, 8, 9, APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06, CCI-000366, 164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e), 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6, A.11.2.6, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.2.1, A.6.2.2, A.9.1.2, CM-7(a), CM-7(b), CM-6(a), PR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4, FMT_SMF_EXT.1.1, SRG-OS-000269-GPOS-00103, SRG-OS-000480-GPOS-00227, UBTU-22-213015, SV-260473r958550_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-213015
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- disable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_kdump_disabled
- name: Disable KDump Kernel Crash Analyzer (kdump) - Collect systemd Services Present
in the System
ansible.builtin.command: systemctl -q list-unit-files --type service
register: service_exists
changed_when: false
failed_when: service_exists.rc not in [0, 1]
check_mode: false
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-213015
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- disable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_kdump_disabled
- name: Disable KDump Kernel Crash Analyzer (kdump) - Ensure kdump-tools.service is
Masked
ansible.builtin.systemd:
name: kdump-tools.service
state: stopped
enabled: false
masked: true
when:
- '"linux-base" in ansible_facts.packages'
- service_exists.stdout_lines is search("kdump-tools.service", multiline=True)
tags:
- DISA-STIG-UBTU-22-213015
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- disable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_kdump_disabled
- name: Unit Socket Exists - kdump-tools.socket
ansible.builtin.command: systemctl -q list-unit-files kdump-tools.socket
register: socket_file_exists
changed_when: false
failed_when: socket_file_exists.rc not in [0, 1]
check_mode: false
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-213015
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- disable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_kdump_disabled
- name: Disable KDump Kernel Crash Analyzer (kdump) - Disable Socket kdump-tools
ansible.builtin.systemd:
name: kdump-tools.socket
enabled: false
state: stopped
masked: true
when:
- '"linux-base" in ansible_facts.packages'
- socket_file_exists.stdout_lines is search("kdump-tools.socket", multiline=True)
tags:
- DISA-STIG-UBTU-22-213015
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- disable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_kdump_disabled
Remediation script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | true |
---|
Strategy: | disable |
---|
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
config:
ignition:
version: 3.1.0
systemd:
units:
- name: kdump-tools.service
enabled: false
mask: true
- name: kdump-tools.socket
enabled: false
mask: true
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include disable_kdump-tools
class disable_kdump-tools {
service {'kdump-tools':
enable => false,
ensure => 'stopped',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
SYSTEMCTL_EXEC='/usr/bin/systemctl'
if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
"$SYSTEMCTL_EXEC" stop 'kdump-tools.service'
fi
"$SYSTEMCTL_EXEC" disable 'kdump-tools.service'
"$SYSTEMCTL_EXEC" mask 'kdump-tools.service'
# Disable socket activation if we have a unit file for it
if "$SYSTEMCTL_EXEC" -q list-unit-files kdump-tools.socket; then
if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
"$SYSTEMCTL_EXEC" stop 'kdump-tools.socket'
fi
"$SYSTEMCTL_EXEC" mask 'kdump-tools.socket'
fi
# The service may not be running because it has been started and failed,
# so let's reset the state so OVAL checks pass.
# Service should be 'inactive', not 'failed' after reboot though.
"$SYSTEMCTL_EXEC" reset-failed 'kdump-tools.service' || true
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[customizations.services]
masked = ["kdump-tools"]
|
Deprecated services
[ref]groupSome deprecated software services impact the overall system security due to their behavior (leak of
confidentiality in network exchange, usage as uncontrolled communication channel, risk associated with the service due to its old age, etc. |
contains 1 rule |
Uninstall the telnet server
[ref]ruleThe telnet daemon should be uninstalled. Rationale:telnet allows clear text communications, and does not protect
any data transmission between client and server. Any confidential data
can be listened and no integrity checking is made.'
References:
11, 12, 14, 15, 3, 8, 9, APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6, A.11.2.6, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.2.1, A.6.2.2, A.9.1.2, CM-7(a), CM-7(b), CM-6(a), PR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4, UBTU-22-215035, SV-260483r987796_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
- name: Ensure telnetd is removed
package:
name: telnetd
state: absent
tags:
- DISA-STIG-UBTU-22-215035
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- disable_strategy
- high_severity
- low_complexity
- low_disruption
- no_reboot_needed
- package_telnetd_removed
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
include remove_telnetd
class remove_telnetd {
package { 'telnetd':
ensure => 'purged',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
# CAUTION: This remediation script will remove telnetd
# from the system, and may remove any packages
# that depend on telnetd. Execute this
# remediation AFTER testing on a non-production
# system!
DEBIAN_FRONTEND=noninteractive apt-get remove -y "telnetd"
|
Network Time Protocol
[ref]groupThe Network Time Protocol is used to manage the system
clock over a network. Computer clocks are not very accurate, so
time will drift unpredictably on unmanaged systems. Central time
protocols can be used both to ensure that time is consistent among
a network of systems, and that their time is consistent with the
outside world.
If every system on a network reliably reports the same time, then it is much
easier to correlate log messages in case of an attack. In addition, a number of
cryptographic protocols (such as Kerberos) use timestamps to prevent certain
types of attacks. If your network does not have synchronized time, these
protocols may be unreliable or even unusable.
Depending on the specifics of the network, global time accuracy may be just as
important as local synchronization, or not very important at all. If your
network is connected to the Internet, using a public timeserver (or one
provided by your enterprise) provides globally accurate timestamps which may be
essential in investigating or responding to an attack which originated outside
of your network.
A typical network setup involves a small number of internal systems operating
as NTP servers, and the remainder obtaining time information from those
internal servers.
There is a choice between the daemons ntpd and chronyd , which
are available from the repositories in the ntp and chrony
packages respectively.
The default chronyd daemon can work well when external time references
are only intermittently accesible, can perform well even when the network is
congested for longer periods of time, can usually synchronize the clock faster
and with better time accuracy, and quickly adapts to sudden changes in the rate
of the clock, for example, due to changes in the temperature of the crystal
oscillator. Chronyd should be considered for all systems which are
frequently suspended or otherwise intermittently disconnected and reconnected
to a network. Mobile and virtual systems for example.
The ntpd NTP daemon fully supports NTP protocol version 4 (RFC 5905),
including broadcast, multicast, manycast clients and servers, and the orphan
mode. It also supports extra authentication schemes based on public-key
cryptography (RFC 5906). The NTP daemon (ntpd ) should be considered
for systems which are normally kept permanently on. Systems which are required
to use broadcast or multicast IP, or to perform authentication of packets with
the Autokey protocol, should consider using ntpd .
Refer to
https://help.ubuntu.com/lts/serverguide/NTP.html
for more detailed comparison of features of chronyd
and ntpd daemon features respectively, and for further guidance how to
choose between the two NTP daemons.
The upstream manual pages at
https://chrony-project.org/documentation.html for
chronyd and
http://www.ntp.org for ntpd provide additional
information on the capabilities and configuration of each of the NTP daemons. |
contains 5 rules |
The Chrony package is installed
[ref]ruleSystem time should be synchronized between all systems in an environment. This is
typically done by establishing an authoritative time server or set of servers and having all
systems synchronize their clocks to them.
The chrony package can be installed with the following command:
$ apt-get install chrony Rationale:Time synchronization is important to support time sensitive security mechanisms like
Kerberos and also ensures log files have consistent time records across the enterprise,
which aids in forensic investigations. References:
R71, 2.1.1.1, CCI-004923, 0988, 1405, FMT_SMF_EXT.1, Req-10.4, 10.6.1, 10.6, SRG-OS-000355-GPOS-00143, UBTU-22-215015, SV-260479r991589_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-215015
- PCI-DSS-Req-10.4
- PCI-DSSv4-10.6
- PCI-DSSv4-10.6.1
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_chrony_installed
- name: Ensure chrony is installed
package:
name: chrony
state: present
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-215015
- PCI-DSS-Req-10.4
- PCI-DSSv4-10.6
- PCI-DSSv4-10.6.1
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_chrony_installed
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_chrony
class install_chrony {
package { 'chrony':
ensure => 'installed',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "chrony"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[[packages]]
name = "chrony"
version = "*"
|
Remove the ntp service
[ref]ruleThe ntpd service should not be installed. Rationale:Inaccurate time stamps make it more difficult to correlate events
and can lead to an inaccurate analysis. Determining the correct
time a particular event occurred on a system is critical when
conducting forensic analysis and investigating system events.
Sources outside the configured acceptable allowance (drift)
may be inaccurate.
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-215025
- disable_strategy
- low_complexity
- low_disruption
- low_severity
- no_reboot_needed
- package_ntp_removed
- name: Ensure ntp is removed
package:
name: ntp
state: absent
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-215025
- disable_strategy
- low_complexity
- low_disruption
- low_severity
- no_reboot_needed
- package_ntp_removed
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
include remove_ntp
class remove_ntp {
package { 'ntp':
ensure => 'purged',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
# CAUTION: This remediation script will remove ntp
# from the system, and may remove any packages
# that depend on ntp. Execute this
# remediation AFTER testing on a non-production
# system!
DEBIAN_FRONTEND=noninteractive apt-get remove -y "ntp"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Remove the systemd_timesyncd Service
[ref]ruleThe systemd_timesyncd service should not be installed. Rationale:Inaccurate time stamps make it more difficult to correlate events
and can lead to an inaccurate analysis. Determining the correct
time a particular event occurred on a system is critical when
conducting forensic analysis and investigating system events.
Sources outside the configured acceptable allowance (drift)
may be inaccurate.
Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-215020
- disable_strategy
- low_complexity
- low_disruption
- low_severity
- no_reboot_needed
- package_timesyncd_removed
- name: Ensure systemd-timesyncd is removed
package:
name: systemd-timesyncd
state: absent
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-215020
- disable_strategy
- low_complexity
- low_disruption
- low_severity
- no_reboot_needed
- package_timesyncd_removed
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
include remove_systemd-timesyncd
class remove_systemd-timesyncd {
package { 'systemd-timesyncd':
ensure => 'purged',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
# CAUTION: This remediation script will remove systemd-timesyncd
# from the system, and may remove any packages
# that depend on systemd-timesyncd. Execute this
# remediation AFTER testing on a non-production
# system!
DEBIAN_FRONTEND=noninteractive apt-get remove -y "systemd-timesyncd"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure Time Service Maxpoll Interval
[ref]ruleThe maxpoll should be configured to
16 in /etc/ntp.conf or
/etc/chrony/chrony.conf (or /etc/chrony/conf.d/ ) to continuously poll time servers. To configure
maxpoll in /etc/ntp.conf or /etc/chrony/chrony.conf (or /etc/chrony/conf.d/ )
add the following after each server , pool or peer entry:
maxpoll 16
to server directives. If using chrony, any pool directives
should be configured too.Rationale:Inaccurate time stamps make it more difficult to correlate events and can lead to an inaccurate analysis. Determining the correct time a particular event occurred on a system is critical when conducting forensic analysis and investigating system events. Sources outside the configured acceptable allowance (drift) may be inaccurate.
Synchronizing internal information system clocks provides uniformity of time stamps for information systems with multiple system clocks and systems connected over a network.
Organizations should consider endpoints that may not have regular access to the authoritative time server (e.g., mobile, teleworking, and tactical endpoints). References:
1, 14, 15, 16, 3, 5, 6, APO11.04, BAI03.05, DSS05.04, DSS05.07, MEA02.01, CCI-001890, CCI-004926, CCI-004923, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, CM-6(a), AU-8(1)(b), AU-12(1), PR.PT-1, SRG-OS-000355-GPOS-00143, SRG-OS-000356-GPOS-00144, SRG-OS-000359-GPOS-00146, UBTU-22-252010, SV-260519r986273_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-252010
- NIST-800-53-AU-12(1)
- NIST-800-53-AU-8(1)(b)
- NIST-800-53-CM-6(a)
- chronyd_or_ntpd_set_maxpoll
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_time_service_set_maxpoll # promote to variable
set_fact:
var_time_service_set_maxpoll: !!str 16
tags:
- always
- name: Configure Time Service Maxpoll Interval - Check That /etc/ntp.conf Exist
ansible.builtin.stat:
path: /etc/ntp.conf
register: ntp_conf_exist_result
when:
- '"linux-base" in ansible_facts.packages'
- ( "chrony" in ansible_facts.packages or "ntp" in ansible_facts.packages )
tags:
- DISA-STIG-UBTU-22-252010
- NIST-800-53-AU-12(1)
- NIST-800-53-AU-8(1)(b)
- NIST-800-53-CM-6(a)
- chronyd_or_ntpd_set_maxpoll
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Configure Time Service Maxpoll Interval - Update the maxpoll Values in /etc/ntp.conf
ansible.builtin.replace:
path: /etc/ntp.conf
regexp: ^(server.*maxpoll)[ ]+[0-9]+(.*)$
replace: \1 {{ var_time_service_set_maxpoll }}\2
when:
- '"linux-base" in ansible_facts.packages'
- ( "chrony" in ansible_facts.packages or "ntp" in ansible_facts.packages )
- ntp_conf_exist_result.stat.exists
tags:
- DISA-STIG-UBTU-22-252010
- NIST-800-53-AU-12(1)
- NIST-800-53-AU-8(1)(b)
- NIST-800-53-CM-6(a)
- chronyd_or_ntpd_set_maxpoll
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Configure Time Service Maxpoll Interval - Set the maxpoll Values in /etc/ntp.conf
ansible.builtin.replace:
path: /etc/ntp.conf
regexp: (^server\s+((?!maxpoll).)*)$
replace: \1 maxpoll {{ var_time_service_set_maxpoll }}\n
when:
- '"linux-base" in ansible_facts.packages'
- ( "chrony" in ansible_facts.packages or "ntp" in ansible_facts.packages )
- ntp_conf_exist_result.stat.exists
tags:
- DISA-STIG-UBTU-22-252010
- NIST-800-53-AU-12(1)
- NIST-800-53-AU-8(1)(b)
- NIST-800-53-CM-6(a)
- chronyd_or_ntpd_set_maxpoll
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Configure Time Service Maxpoll Interval - Check That /etc/chrony/chrony.conf
Exist
ansible.builtin.stat:
path: /etc/chrony/chrony.conf
register: chrony_conf_exist_result
when:
- '"linux-base" in ansible_facts.packages'
- ( "chrony" in ansible_facts.packages or "ntp" in ansible_facts.packages )
tags:
- DISA-STIG-UBTU-22-252010
- NIST-800-53-AU-12(1)
- NIST-800-53-AU-8(1)(b)
- NIST-800-53-CM-6(a)
- chronyd_or_ntpd_set_maxpoll
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Configure Time Service Maxpoll Interval - Update the maxpoll Values in /etc/chrony/chrony.conf
ansible.builtin.replace:
path: /etc/chrony/chrony.conf
regexp: ^((?:server|pool|peer).*maxpoll)[ ]+[0-9]+(.*)$
replace: \1 {{ var_time_service_set_maxpoll }}\2
when:
- '"linux-base" in ansible_facts.packages'
- ( "chrony" in ansible_facts.packages or "ntp" in ansible_facts.packages )
- chrony_conf_exist_result.stat.exists
tags:
- DISA-STIG-UBTU-22-252010
- NIST-800-53-AU-12(1)
- NIST-800-53-AU-8(1)(b)
- NIST-800-53-CM-6(a)
- chronyd_or_ntpd_set_maxpoll
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Configure Time Service Maxpoll Interval - Set the maxpoll Values in /etc/chrony/chrony.conf
ansible.builtin.replace:
path: /etc/chrony/chrony.conf
regexp: (^(?:server|pool|peer)\s+((?!maxpoll).)*)$
replace: \1 maxpoll {{ var_time_service_set_maxpoll }}\n
when:
- '"linux-base" in ansible_facts.packages'
- ( "chrony" in ansible_facts.packages or "ntp" in ansible_facts.packages )
- chrony_conf_exist_result.stat.exists
tags:
- DISA-STIG-UBTU-22-252010
- NIST-800-53-AU-12(1)
- NIST-800-53-AU-8(1)(b)
- NIST-800-53-CM-6(a)
- chronyd_or_ntpd_set_maxpoll
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Configure Time Service Maxpoll Interval - Get Conf Files from /etc/chrony/conf.d/
ansible.builtin.find:
path: /etc/chrony/conf.d/
patterns: '*.conf'
file_type: file
register: chrony_d_conf_files
when:
- '"linux-base" in ansible_facts.packages'
- ( "chrony" in ansible_facts.packages or "ntp" in ansible_facts.packages )
tags:
- DISA-STIG-UBTU-22-252010
- NIST-800-53-AU-12(1)
- NIST-800-53-AU-8(1)(b)
- NIST-800-53-CM-6(a)
- chronyd_or_ntpd_set_maxpoll
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Configure Time Service Maxpoll Interval - Update the maxpoll Values in /etc/chrony/conf.d/
ansible.builtin.replace:
path: '{{ item.path }}'
regexp: ^((?:server|pool|peer).*maxpoll)[ ]+[0-9,-]+(.*)$
replace: \1 {{ var_time_service_set_maxpoll }}\2
loop: '{{ chrony_d_conf_files.files }}'
when:
- '"linux-base" in ansible_facts.packages'
- ( "chrony" in ansible_facts.packages or "ntp" in ansible_facts.packages )
- chrony_d_conf_files.matched
tags:
- DISA-STIG-UBTU-22-252010
- NIST-800-53-AU-12(1)
- NIST-800-53-AU-8(1)(b)
- NIST-800-53-CM-6(a)
- chronyd_or_ntpd_set_maxpoll
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Configure Time Service Maxpoll Interval - Set the maxpoll Values in /etc/chrony/conf.d/
ansible.builtin.replace:
path: '{{ item.path }}'
regexp: (^(?:server|pool|peer)\s+((?!maxpoll).)*)$
replace: \1 maxpoll {{ var_time_service_set_maxpoll }}\n
loop: '{{ chrony_d_conf_files.files }}'
when:
- '"linux-base" in ansible_facts.packages'
- ( "chrony" in ansible_facts.packages or "ntp" in ansible_facts.packages )
- chrony_d_conf_files.matched
tags:
- DISA-STIG-UBTU-22-252010
- NIST-800-53-AU-12(1)
- NIST-800-53-AU-8(1)(b)
- NIST-800-53-CM-6(a)
- chronyd_or_ntpd_set_maxpoll
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && { ( dpkg-query --show --showformat='${db:Status-Status}\n' 'chrony' 2>/dev/null | grep -q '^installed' || dpkg-query --show --showformat='${db:Status-Status}\n' 'ntp' 2>/dev/null | grep -q '^installed' ); }; then
var_time_service_set_maxpoll='16'
pof="/bin/pidof"
CONFIG_FILES="/etc/ntp.conf"
$pof ntpd || {
CHRONY_D_PATH=/etc/chrony/conf.d/
mapfile -t CONFIG_FILES < <(find ${CHRONY_D_PATH}.* -type f -name '*.conf')
CONFIG_FILES+=(/etc/chrony/chrony.conf)
}
# get list of ntp files
for config_file in "${CONFIG_FILES[@]}" ; do
# Set maxpoll values to var_time_service_set_maxpoll
sed -i "s/^\(\(server\|pool\|peer\).*maxpoll\) [0-9,-][0-9]*\(.*\)$/\1 $var_time_service_set_maxpoll \3/" "$config_file"
done
for config_file in "${CONFIG_FILES[@]}" ; do
# Add maxpoll to server, pool or peer entries without maxpoll
grep "^\(server\|pool\|peer\)" "$config_file" | grep -v maxpoll | while read -r line ; do
sed -i "s/$line/& maxpoll $var_time_service_set_maxpoll/" "$config_file"
done
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Synchronize internal information system clocks
[ref]ruleSynchronizing internal information system clocks provides uniformity
of time stamps for information systems with multiple system clocks and
systems connected over a network. Rationale:Inaccurate time stamps make it more difficult to correlate events and
can lead to an inaccurate analysis. Determining the correct time a
particular event occurred on a system is critical when conducting
forensic analysis and investigating system events. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-252015
- chronyd_sync_clock
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Synchronize internal information system clocks
block:
- name: Check for duplicate values
lineinfile:
path: /etc/chrony/chrony.conf
create: true
regexp: '(?i)^\s*makestep '
state: absent
check_mode: true
changed_when: false
register: dupes
- name: Deduplicate values from /etc/chrony/chrony.conf
lineinfile:
path: /etc/chrony/chrony.conf
create: true
regexp: '(?i)^\s*makestep '
state: absent
when: dupes.found is defined and dupes.found > 1
- name: Insert correct line to /etc/chrony/chrony.conf
lineinfile:
path: /etc/chrony/chrony.conf
create: true
regexp: '(?i)^\s*makestep '
line: makestep 1 1
state: present
when:
- '"linux-base" in ansible_facts.packages'
- '"chrony" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-252015
- chronyd_sync_clock
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && { dpkg-query --show --showformat='${db:Status-Status}\n' 'chrony' 2>/dev/null | grep -q '^installed'; }; then
if [ -e "/etc/chrony/chrony.conf" ] ; then
LC_ALL=C sed -i "/^\s*makestep /Id" "/etc/chrony/chrony.conf"
else
touch "/etc/chrony/chrony.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/chrony/chrony.conf"
cp "/etc/chrony/chrony.conf" "/etc/chrony/chrony.conf.bak"
# Insert at the end of the file
printf '%s\n' "makestep 1 1" >> "/etc/chrony/chrony.conf"
# Clean up after ourselves.
rm "/etc/chrony/chrony.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Obsolete Services
[ref]groupThis section discusses a number of network-visible
services which have historically caused problems for system
security, and for which disabling or severely limiting the service
has been the best available guidance for some time. As a result of
this, many of these services are not installed as part of Ubuntu 22.04
by default.
Organizations which are running these services should
switch to more secure equivalents as soon as possible.
If it remains absolutely necessary to run one of
these services for legacy reasons, care should be taken to restrict
the service as much as possible, for instance by configuring host
firewall software such as iptables to restrict access to the
vulnerable service to only those remote hosts which have a known
need to use it. |
contains 1 rule |
Rlogin, Rsh, and Rexec
[ref]groupThe Berkeley r-commands are legacy services which
allow cleartext remote access and have an insecure trust
model. |
contains 1 rule |
Uninstall rsh-server Package
[ref]ruleThe rsh-server package can be removed with the following command:
$ apt-get remove rsh-server Rationale:The rsh-server service provides unencrypted remote access service which does not
provide for the confidentiality and integrity of user passwords or the remote session and has very weak
authentication. If a privileged user were to login using this service, the privileged user password
could be compromised. The rsh-server package provides several obsolete and insecure
network services. Removing it decreases the risk of those services' accidental (or intentional)
activation. References:
R62, 11, 12, 14, 15, 3, 8, 9, APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06, CCI-000381, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6, A.11.2.6, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.2.1, A.6.2.2, A.9.1.2, CM-7(a), CM-7(b), CM-6(a), IA-5(1)(c), PR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4, 2.2.4, 2.2, SRG-OS-000095-GPOS-00049, UBTU-22-215030, SV-260482r958478_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
- name: Ensure rsh-server is removed
package:
name: rsh-server
state: absent
tags:
- DISA-STIG-UBTU-22-215030
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- NIST-800-53-IA-5(1)(c)
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.4
- disable_strategy
- high_severity
- low_complexity
- low_disruption
- no_reboot_needed
- package_rsh-server_removed
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
include remove_rsh-server
class remove_rsh-server {
package { 'rsh-server':
ensure => 'purged',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | disable |
---|
# CAUTION: This remediation script will remove rsh-server
# from the system, and may remove any packages
# that depend on rsh-server. Execute this
# remediation AFTER testing on a non-production
# system!
DEBIAN_FRONTEND=noninteractive apt-get remove -y "rsh-server"
|
SSH Server
[ref]groupThe SSH protocol is recommended for remote login and
remote file transfer. SSH provides confidentiality and integrity
for data exchanged between two systems, as well as server
authentication, through the use of public key cryptography. The
implementation included with the system is called OpenSSH, and more
detailed documentation is available from its website,
https://www.openssh.com.
Its server program is called sshd and provided by the RPM package
openssh-server . |
contains 14 rules |
Configure OpenSSH Server if Necessary
[ref]groupIf the system needs to act as an SSH server, then
certain changes should be made to the OpenSSH daemon configuration
file /etc/ssh/sshd_config . The following recommendations can be
applied to this file. See the sshd_config(5) man page for more
detailed information. |
contains 12 rules |
Set SSH Client Alive Count Max
[ref]ruleThe SSH server sends at most ClientAliveCountMax messages
during a SSH session and waits for a response from the SSH client.
The option ClientAliveInterval configures timeout after
each ClientAliveCountMax message. If the SSH server does not
receive a response from the client, then the connection is considered unresponsive
and terminated.
For SSH earlier than v8.2, a ClientAliveCountMax value of 0
causes a timeout precisely when the ClientAliveInterval is set.
Starting with v8.2, a value of 0 disables the timeout functionality
completely. If the option is set to a number greater than 0 , then
the session will be disconnected after
ClientAliveInterval * ClientAliveCountMax seconds without receiving
a keep alive message. Rationale:This ensures a user login will be terminated as soon as the ClientAliveInterval
is reached. References:
5.2.22, 1, 12, 13, 14, 15, 16, 18, 3, 5, 7, 8, 5.5.6, APO13.01, BAI03.01, BAI03.02, BAI03.03, DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, 3.1.11, CCI-001133, CCI-002361, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.3, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 6.2, A.12.4.1, A.12.4.3, A.14.1.1, A.14.2.1, A.14.2.5, A.18.1.4, A.6.1.2, A.6.1.5, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, CIP-004-6 R2.2.3, CIP-007-3 R5.1, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3, AC-2(5), AC-12, AC-17(a), SC-10, CM-6(a), DE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.IP-2, Req-8.1.8, 8.2.8, 8.2, SRG-OS-000163-GPOS-00072, SRG-OS-000279-GPOS-00109, UBTU-22-255030, SV-260527r986275_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-22-255030
- NIST-800-171-3.1.11
- NIST-800-53-AC-12
- NIST-800-53-AC-17(a)
- NIST-800-53-AC-2(5)
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-10
- PCI-DSS-Req-8.1.8
- PCI-DSSv4-8.2
- PCI-DSSv4-8.2.8
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_set_keepalive
- name: XCCDF Value var_sshd_set_keepalive # promote to variable
set_fact:
var_sshd_set_keepalive: !!str 1
tags:
- always
- name: Set SSH Client Alive Count Max
block:
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)(?i)^\s*{{ "ClientAliveCountMax"| regex_escape }}\s+
state: absent
- name: Check if /etc/ssh/sshd_config.d exists
stat:
path: /etc/ssh/sshd_config.d
register: _etc_ssh_sshd_config_d_exists
- name: Check if the parameter ClientAliveCountMax is present in /etc/ssh/sshd_config.d
find:
paths: /etc/ssh/sshd_config.d
recurse: 'yes'
follow: 'no'
contains: (?i)^\s*{{ "ClientAliveCountMax"| regex_escape }}\s+
register: _etc_ssh_sshd_config_d_has_parameter
when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir
- name: Remove parameter from files in /etc/ssh/sshd_config.d
lineinfile:
path: '{{ item.path }}'
create: false
regexp: (?i)(?i)^\s*{{ "ClientAliveCountMax"| regex_escape }}\s+
state: absent
with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
when: _etc_ssh_sshd_config_d_has_parameter.matched
- name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
lineinfile:
path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
create: true
regexp: (?i)(?i)^\s*{{ "ClientAliveCountMax"| regex_escape }}\s+
line: ClientAliveCountMax {{ var_sshd_set_keepalive }}
state: present
insertbefore: BOF
validate: /usr/sbin/sshd -t -f %s
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-22-255030
- NIST-800-171-3.1.11
- NIST-800-53-AC-12
- NIST-800-53-AC-17(a)
- NIST-800-53-AC-2(5)
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-10
- PCI-DSS-Req-8.1.8
- PCI-DSSv4-8.2
- PCI-DSSv4-8.2.8
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_set_keepalive
- name: Set SSH Client Alive Count Max - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
ansible.builtin.file:
path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
mode: '0600'
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-22-255030
- NIST-800-171-3.1.11
- NIST-800-53-AC-12
- NIST-800-53-AC-17(a)
- NIST-800-53-AC-2(5)
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-10
- PCI-DSS-Req-8.1.8
- PCI-DSSv4-8.2
- PCI-DSSv4-8.2.8
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_set_keepalive
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
var_sshd_set_keepalive='1'
mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
LC_ALL=C sed -i "/^\s*ClientAliveCountMax\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*ClientAliveCountMax\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then
LC_ALL=C sed -i "/^\s*ClientAliveCountMax\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
else
touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "ClientAliveCountMax $var_sshd_set_keepalive" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Set SSH Client Alive Interval
[ref]ruleSSH allows administrators to set a network responsiveness timeout interval.
After this interval has passed, the unresponsive client will be automatically logged out.
To set this timeout interval, edit the following line in /etc/ssh/sshd_config as
follows:
ClientAliveInterval 600
The timeout interval is given in seconds. For example, have a timeout
of 10 minutes, set interval to 600.
If a shorter timeout has already been set for the login shell, that value will
preempt any SSH setting made in /etc/ssh/sshd_config . Keep in mind that
some processes may stop SSH from correctly detecting that the user is idle.Warning:
SSH disconnecting unresponsive clients will not have desired effect without also
configuring ClientAliveCountMax in the SSH service configuration. Warning:
Following conditions may prevent the SSH session to time out:
- Remote processes on the remote machine generates output. As the output has to be transferred over the network to the client, the timeout is reset every time such transfer happens.
- Any
scp or sftp activity by the same user to the host resets the timeout.
Rationale:Terminating an idle ssh session within a short time period reduces the window of
opportunity for unauthorized personnel to take control of a management session
enabled on the console or console port that has been let unattended. References:
5.2.22, 1, 12, 13, 14, 15, 16, 18, 3, 5, 7, 8, 5.5.6, APO13.01, BAI03.01, BAI03.02, BAI03.03, DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, 3.1.11, CCI-001133, CCI-002361, CCI-002891, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.3, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 6.2, A.12.4.1, A.12.4.3, A.14.1.1, A.14.2.1, A.14.2.5, A.18.1.4, A.6.1.2, A.6.1.5, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, CIP-004-6 R2.2.3, CIP-007-3 R5.1, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3, CM-6(a), AC-17(a), AC-2(5), AC-12, AC-17(a), SC-10, CM-6(a), DE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.IP-2, Req-8.1.8, 8.2.8, 8.2, SRG-OS-000126-GPOS-00066, SRG-OS-000163-GPOS-00072, SRG-OS-000279-GPOS-00109, SRG-OS-000395-GPOS-00175, UBTU-22-255035, SV-260528r970703_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-22-255035
- NIST-800-171-3.1.11
- NIST-800-53-AC-12
- NIST-800-53-AC-17(a)
- NIST-800-53-AC-17(a)
- NIST-800-53-AC-2(5)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-10
- PCI-DSS-Req-8.1.8
- PCI-DSSv4-8.2
- PCI-DSSv4-8.2.8
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_set_idle_timeout
- name: XCCDF Value sshd_idle_timeout_value # promote to variable
set_fact:
sshd_idle_timeout_value: !!str 600
tags:
- always
- name: Set SSH Client Alive Interval
block:
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)(?i)^\s*{{ "ClientAliveInterval"| regex_escape }}\s+
state: absent
- name: Check if /etc/ssh/sshd_config.d exists
stat:
path: /etc/ssh/sshd_config.d
register: _etc_ssh_sshd_config_d_exists
- name: Check if the parameter ClientAliveInterval is present in /etc/ssh/sshd_config.d
find:
paths: /etc/ssh/sshd_config.d
recurse: 'yes'
follow: 'no'
contains: (?i)^\s*{{ "ClientAliveInterval"| regex_escape }}\s+
register: _etc_ssh_sshd_config_d_has_parameter
when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir
- name: Remove parameter from files in /etc/ssh/sshd_config.d
lineinfile:
path: '{{ item.path }}'
create: false
regexp: (?i)(?i)^\s*{{ "ClientAliveInterval"| regex_escape }}\s+
state: absent
with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
when: _etc_ssh_sshd_config_d_has_parameter.matched
- name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
lineinfile:
path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
create: true
regexp: (?i)(?i)^\s*{{ "ClientAliveInterval"| regex_escape }}\s+
line: ClientAliveInterval {{ sshd_idle_timeout_value }}
state: present
insertbefore: BOF
validate: /usr/sbin/sshd -t -f %s
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-22-255035
- NIST-800-171-3.1.11
- NIST-800-53-AC-12
- NIST-800-53-AC-17(a)
- NIST-800-53-AC-17(a)
- NIST-800-53-AC-2(5)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-10
- PCI-DSS-Req-8.1.8
- PCI-DSSv4-8.2
- PCI-DSSv4-8.2.8
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_set_idle_timeout
- name: Set SSH Client Alive Interval - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
ansible.builtin.file:
path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
mode: '0600'
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-22-255035
- NIST-800-171-3.1.11
- NIST-800-53-AC-12
- NIST-800-53-AC-17(a)
- NIST-800-53-AC-17(a)
- NIST-800-53-AC-2(5)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-10
- PCI-DSS-Req-8.1.8
- PCI-DSSv4-8.2
- PCI-DSSv4-8.2.8
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_set_idle_timeout
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
sshd_idle_timeout_value='600'
mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
LC_ALL=C sed -i "/^\s*ClientAliveInterval\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*ClientAliveInterval\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then
LC_ALL=C sed -i "/^\s*ClientAliveInterval\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
else
touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "ClientAliveInterval $sshd_idle_timeout_value" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Disable SSH Access via Empty Passwords
[ref]ruleDisallow SSH login with empty passwords.
The default SSH configuration disables logins with empty passwords. The appropriate
configuration is used if no value is set for PermitEmptyPasswords .
To explicitly disallow SSH login from accounts with empty passwords,
add or correct the following line in
/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf :
PermitEmptyPasswords no
Any accounts with empty passwords should be disabled immediately, and PAM configuration
should prevent users from being able to assign themselves empty passwords.Rationale:Configuring this setting for the SSH daemon provides additional assurance
that remote login via SSH will require a password, even in the event of
misconfiguration elsewhere. References:
5.2.9, 11, 12, 13, 14, 15, 16, 18, 3, 5, 9, 5.5.6, APO01.06, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.03, DSS06.06, 3.1.1, 3.1.5, CCI-000766, CCI-000366, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 5.2, SR 7.6, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.1, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, AC-17(a), CM-7(a), CM-7(b), CM-6(a), PR.AC-4, PR.AC-6, PR.DS-5, PR.IP-1, PR.PT-3, FIA_UAU.1, Req-2.2.4, 2.2.6, 2.2, SRG-OS-000106-GPOS-00053, SRG-OS-000480-GPOS-00229, SRG-OS-000480-GPOS-00227, UBTU-22-255025, SV-260526r991591_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-22-255025
- NIST-800-171-3.1.1
- NIST-800-171-3.1.5
- NIST-800-53-AC-17(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- PCI-DSS-Req-2.2.4
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.6
- high_severity
- low_complexity
- low_disruption
- no_reboot_needed
- restrict_strategy
- sshd_disable_empty_passwords
- name: Disable SSH Access via Empty Passwords
block:
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)(?i)^\s*{{ "PermitEmptyPasswords"| regex_escape }}\s+
state: absent
- name: Check if /etc/ssh/sshd_config.d exists
stat:
path: /etc/ssh/sshd_config.d
register: _etc_ssh_sshd_config_d_exists
- name: Check if the parameter PermitEmptyPasswords is present in /etc/ssh/sshd_config.d
find:
paths: /etc/ssh/sshd_config.d
recurse: 'yes'
follow: 'no'
contains: (?i)^\s*{{ "PermitEmptyPasswords"| regex_escape }}\s+
register: _etc_ssh_sshd_config_d_has_parameter
when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir
- name: Remove parameter from files in /etc/ssh/sshd_config.d
lineinfile:
path: '{{ item.path }}'
create: false
regexp: (?i)(?i)^\s*{{ "PermitEmptyPasswords"| regex_escape }}\s+
state: absent
with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
when: _etc_ssh_sshd_config_d_has_parameter.matched
- name: Insert correct line to /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
lineinfile:
path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
create: true
regexp: (?i)(?i)^\s*{{ "PermitEmptyPasswords"| regex_escape }}\s+
line: PermitEmptyPasswords no
state: present
insertbefore: BOF
validate: /usr/sbin/sshd -t -f %s
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-22-255025
- NIST-800-171-3.1.1
- NIST-800-171-3.1.5
- NIST-800-53-AC-17(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- PCI-DSS-Req-2.2.4
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.6
- high_severity
- low_complexity
- low_disruption
- no_reboot_needed
- restrict_strategy
- sshd_disable_empty_passwords
- name: Disable SSH Access via Empty Passwords - set file mode for /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
ansible.builtin.file:
path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
mode: '0600'
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-22-255025
- NIST-800-171-3.1.1
- NIST-800-171-3.1.5
- NIST-800-53-AC-17(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- PCI-DSS-Req-2.2.4
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.6
- high_severity
- low_complexity
- low_disruption
- no_reboot_needed
- restrict_strategy
- sshd_disable_empty_passwords
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
chmod 0600 /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
LC_ALL=C sed -i "/^\s*PermitEmptyPasswords\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*PermitEmptyPasswords\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" ] ; then
LC_ALL=C sed -i "/^\s*PermitEmptyPasswords\s\+/Id" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
else
touch "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
cp "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "PermitEmptyPasswords no" > "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
cat "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak" >> "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Disable X11 Forwarding
[ref]ruleThe X11Forwarding parameter provides the ability to tunnel X11 traffic
through the connection to enable remote graphic connections.
SSH has the capability to encrypt remote X11 connections when SSH's
X11Forwarding option is enabled.
The default SSH configuration disables X11Forwarding. The appropriate
configuration is used if no value is set for X11Forwarding .
To explicitly disable X11 Forwarding, add or correct the following line in
/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf :
X11Forwarding no Rationale:Disable X11 forwarding unless there is an operational requirement to use X11
applications directly. There is a small risk that the remote X11 servers of
users who are logged in via SSH with X11 forwarding could be compromised by
other users on the X11 server. Note that even if X11 forwarding is disabled,
users can always install their own forwarders. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-255040
- NIST-800-53-CM-6(b)
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.6
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_disable_x11_forwarding
- name: Disable X11 Forwarding
block:
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)(?i)^\s*{{ "X11Forwarding"| regex_escape }}\s+
state: absent
- name: Check if /etc/ssh/sshd_config.d exists
stat:
path: /etc/ssh/sshd_config.d
register: _etc_ssh_sshd_config_d_exists
- name: Check if the parameter X11Forwarding is present in /etc/ssh/sshd_config.d
find:
paths: /etc/ssh/sshd_config.d
recurse: 'yes'
follow: 'no'
contains: (?i)^\s*{{ "X11Forwarding"| regex_escape }}\s+
register: _etc_ssh_sshd_config_d_has_parameter
when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir
- name: Remove parameter from files in /etc/ssh/sshd_config.d
lineinfile:
path: '{{ item.path }}'
create: false
regexp: (?i)(?i)^\s*{{ "X11Forwarding"| regex_escape }}\s+
state: absent
with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
when: _etc_ssh_sshd_config_d_has_parameter.matched
- name: Insert correct line to /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
lineinfile:
path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
create: true
regexp: (?i)(?i)^\s*{{ "X11Forwarding"| regex_escape }}\s+
line: X11Forwarding no
state: present
insertbefore: BOF
validate: /usr/sbin/sshd -t -f %s
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-255040
- NIST-800-53-CM-6(b)
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.6
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_disable_x11_forwarding
- name: Disable X11 Forwarding - set file mode for /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
ansible.builtin.file:
path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
mode: '0600'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-255040
- NIST-800-53-CM-6(b)
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.6
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_disable_x11_forwarding
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
chmod 0600 /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
LC_ALL=C sed -i "/^\s*X11Forwarding\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*X11Forwarding\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" ] ; then
LC_ALL=C sed -i "/^\s*X11Forwarding\s\+/Id" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
else
touch "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
cp "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "X11Forwarding no" > "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
cat "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak" >> "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Do Not Allow SSH Environment Options
[ref]ruleEnsure that users are not able to override environment variables of the SSH daemon.
The default SSH configuration disables environment processing. The appropriate
configuration is used if no value is set for PermitUserEnvironment .
To explicitly disable Environment options, add or correct the following
/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf :
PermitUserEnvironment no Rationale:SSH environment options potentially allow users to bypass
access restriction in some configurations. References:
5.2.10, 11, 3, 9, 5.5.6, BAI10.01, BAI10.02, BAI10.03, BAI10.05, 3.1.12, CCI-000366, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), 4.3.4.3.2, 4.3.4.3.3, SR 7.6, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, AC-17(a), CM-7(a), CM-7(b), CM-6(a), PR.IP-1, Req-2.2.4, 2.2.6, 2.2, SRG-OS-000480-GPOS-00229, UBTU-22-255025, SV-260526r991591_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-22-255025
- NIST-800-171-3.1.12
- NIST-800-53-AC-17(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- PCI-DSS-Req-2.2.4
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.6
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_do_not_permit_user_env
- name: Do Not Allow SSH Environment Options
block:
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)(?i)^\s*{{ "PermitUserEnvironment"| regex_escape }}\s+
state: absent
- name: Check if /etc/ssh/sshd_config.d exists
stat:
path: /etc/ssh/sshd_config.d
register: _etc_ssh_sshd_config_d_exists
- name: Check if the parameter PermitUserEnvironment is present in /etc/ssh/sshd_config.d
find:
paths: /etc/ssh/sshd_config.d
recurse: 'yes'
follow: 'no'
contains: (?i)^\s*{{ "PermitUserEnvironment"| regex_escape }}\s+
register: _etc_ssh_sshd_config_d_has_parameter
when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir
- name: Remove parameter from files in /etc/ssh/sshd_config.d
lineinfile:
path: '{{ item.path }}'
create: false
regexp: (?i)(?i)^\s*{{ "PermitUserEnvironment"| regex_escape }}\s+
state: absent
with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
when: _etc_ssh_sshd_config_d_has_parameter.matched
- name: Insert correct line to /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
lineinfile:
path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
create: true
regexp: (?i)(?i)^\s*{{ "PermitUserEnvironment"| regex_escape }}\s+
line: PermitUserEnvironment no
state: present
insertbefore: BOF
validate: /usr/sbin/sshd -t -f %s
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-22-255025
- NIST-800-171-3.1.12
- NIST-800-53-AC-17(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- PCI-DSS-Req-2.2.4
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.6
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_do_not_permit_user_env
- name: Do Not Allow SSH Environment Options - set file mode for /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
ansible.builtin.file:
path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
mode: '0600'
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-22-255025
- NIST-800-171-3.1.12
- NIST-800-53-AC-17(a)
- NIST-800-53-CM-6(a)
- NIST-800-53-CM-7(a)
- NIST-800-53-CM-7(b)
- PCI-DSS-Req-2.2.4
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.6
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_do_not_permit_user_env
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
chmod 0600 /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
LC_ALL=C sed -i "/^\s*PermitUserEnvironment\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*PermitUserEnvironment\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" ] ; then
LC_ALL=C sed -i "/^\s*PermitUserEnvironment\s\+/Id" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
else
touch "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
cp "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "PermitUserEnvironment no" > "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
cat "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak" >> "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Enable PAM
[ref]ruleUsePAM Enables the Pluggable Authentication Module interface. If set to “yes” this will
enable PAM authentication using ChallengeResponseAuthentication and
PasswordAuthentication in addition to PAM account and session module processing for all
authentication types.
To enable PAM authentication, add or correct the following line in
/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf :
UsePAM yes Rationale:When UsePAM is set to yes, PAM runs through account and session types properly. This is
important if you want to restrict access to services based off of IP, time or other factors of
the account. Additionally, you can make sure users inherit certain environment variables
on login or disallow access to the server. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-255065
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.6
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_enable_pam
- name: Enable PAM
block:
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)(?i)^\s*{{ "UsePAM"| regex_escape }}\s+
state: absent
- name: Check if /etc/ssh/sshd_config.d exists
stat:
path: /etc/ssh/sshd_config.d
register: _etc_ssh_sshd_config_d_exists
- name: Check if the parameter UsePAM is present in /etc/ssh/sshd_config.d
find:
paths: /etc/ssh/sshd_config.d
recurse: 'yes'
follow: 'no'
contains: (?i)^\s*{{ "UsePAM"| regex_escape }}\s+
register: _etc_ssh_sshd_config_d_has_parameter
when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir
- name: Remove parameter from files in /etc/ssh/sshd_config.d
lineinfile:
path: '{{ item.path }}'
create: false
regexp: (?i)(?i)^\s*{{ "UsePAM"| regex_escape }}\s+
state: absent
with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
when: _etc_ssh_sshd_config_d_has_parameter.matched
- name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
lineinfile:
path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
create: true
regexp: (?i)(?i)^\s*{{ "UsePAM"| regex_escape }}\s+
line: UsePAM yes
state: present
insertbefore: BOF
validate: /usr/sbin/sshd -t -f %s
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-255065
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.6
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_enable_pam
- name: Enable PAM - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
ansible.builtin.file:
path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
mode: '0600'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-255065
- PCI-DSSv4-2.2
- PCI-DSSv4-2.2.6
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_enable_pam
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
LC_ALL=C sed -i "/^\s*UsePAM\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*UsePAM\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then
LC_ALL=C sed -i "/^\s*UsePAM\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
else
touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "UsePAM yes" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Enable Public Key Authentication
[ref]ruleEnable SSH login with public keys.
The default SSH configuration enables authentication based on public keys. The appropriate
configuration is used if no value is set for PubkeyAuthentication .
To explicitly enable Public Key Authentication, add or correct the following
/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf :
PubkeyAuthentication yes Rationale:Without the use of multifactor authentication, the ease of access to
privileged functions is greatly increased. Multifactor authentication
requires using two or more factors to achieve authentication.
A privileged account is defined as an information system account with
authorizations of a privileged user.
The DoD CAC with DoD-approved PKI is an example of multifactor
authentication. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-612020
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_enable_pubkey_auth
- name: Enable Public Key Authentication
block:
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)(?i)^\s*{{ "PubkeyAuthentication"| regex_escape }}\s+
state: absent
- name: Check if /etc/ssh/sshd_config.d exists
stat:
path: /etc/ssh/sshd_config.d
register: _etc_ssh_sshd_config_d_exists
- name: Check if the parameter PubkeyAuthentication is present in /etc/ssh/sshd_config.d
find:
paths: /etc/ssh/sshd_config.d
recurse: 'yes'
follow: 'no'
contains: (?i)^\s*{{ "PubkeyAuthentication"| regex_escape }}\s+
register: _etc_ssh_sshd_config_d_has_parameter
when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir
- name: Remove parameter from files in /etc/ssh/sshd_config.d
lineinfile:
path: '{{ item.path }}'
create: false
regexp: (?i)(?i)^\s*{{ "PubkeyAuthentication"| regex_escape }}\s+
state: absent
with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
when: _etc_ssh_sshd_config_d_has_parameter.matched
- name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
lineinfile:
path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
create: true
regexp: (?i)(?i)^\s*{{ "PubkeyAuthentication"| regex_escape }}\s+
line: PubkeyAuthentication yes
state: present
insertbefore: BOF
validate: /usr/sbin/sshd -t -f %s
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-612020
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_enable_pubkey_auth
- name: Enable Public Key Authentication - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
ansible.builtin.file:
path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
mode: '0600'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-612020
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_enable_pubkey_auth
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
LC_ALL=C sed -i "/^\s*PubkeyAuthentication\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*PubkeyAuthentication\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then
LC_ALL=C sed -i "/^\s*PubkeyAuthentication\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
else
touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "PubkeyAuthentication yes" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Enable SSH Warning Banner
[ref]ruleTo enable the warning banner and ensure it is consistent
across the system, add or correct the following line in
/etc/ssh/sshd_config :
Banner /etc/issue.net
Another section contains information on how to create an
appropriate system-wide warning banner.Rationale:The warning message reinforces policy awareness during the logon process and
facilitates possible legal action against attackers. Alternatively, systems
whose ownership should not be obvious should ensure usage of a banner that does
not provide easy attribution. References:
5.2.17, 5.5.6, DSS05.04, DSS05.10, DSS06.10, 3.1.9, CCI-000048, CCI-000050, CCI-001384, CCI-001385, CCI-001386, CCI-001387, CCI-001388, 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii), 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9, A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3, AC-8(a), AC-8(c), AC-17(a), CM-6(a), PR.AC-7, SRG-OS-000023-GPOS-00006, SRG-OS-000228-GPOS-00088, UBTU-22-255020, SV-260525r958390_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-22-255020
- NIST-800-171-3.1.9
- NIST-800-53-AC-17(a)
- NIST-800-53-AC-8(a)
- NIST-800-53-AC-8(c)
- NIST-800-53-CM-6(a)
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_enable_warning_banner_net
- name: Enable SSH Warning Banner
block:
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)(?i)^\s*{{ "Banner"| regex_escape }}\s+
state: absent
- name: Check if /etc/ssh/sshd_config.d exists
stat:
path: /etc/ssh/sshd_config.d
register: _etc_ssh_sshd_config_d_exists
- name: Check if the parameter Banner is present in /etc/ssh/sshd_config.d
find:
paths: /etc/ssh/sshd_config.d
recurse: 'yes'
follow: 'no'
contains: (?i)^\s*{{ "Banner"| regex_escape }}\s+
register: _etc_ssh_sshd_config_d_has_parameter
when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir
- name: Remove parameter from files in /etc/ssh/sshd_config.d
lineinfile:
path: '{{ item.path }}'
create: false
regexp: (?i)(?i)^\s*{{ "Banner"| regex_escape }}\s+
state: absent
with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
when: _etc_ssh_sshd_config_d_has_parameter.matched
- name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
lineinfile:
path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
create: true
regexp: (?i)(?i)^\s*{{ "Banner"| regex_escape }}\s+
line: Banner /etc/issue.net
state: present
insertbefore: BOF
validate: /usr/sbin/sshd -t -f %s
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-22-255020
- NIST-800-171-3.1.9
- NIST-800-53-AC-17(a)
- NIST-800-53-AC-8(a)
- NIST-800-53-AC-8(c)
- NIST-800-53-CM-6(a)
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_enable_warning_banner_net
- name: Enable SSH Warning Banner - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
ansible.builtin.file:
path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
mode: '0600'
when: '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.5.6
- DISA-STIG-UBTU-22-255020
- NIST-800-171-3.1.9
- NIST-800-53-AC-17(a)
- NIST-800-53-AC-8(a)
- NIST-800-53-AC-8(c)
- NIST-800-53-CM-6(a)
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_enable_warning_banner_net
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
LC_ALL=C sed -i "/^\s*Banner\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*Banner\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then
LC_ALL=C sed -i "/^\s*Banner\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
else
touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "Banner /etc/issue.net" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Use Only FIPS 140-2 Validated Ciphers
[ref]ruleLimit the ciphers to those algorithms which are FIPS-approved.
The following line in /etc/ssh/sshd_config
demonstrates use of FIPS-approved ciphers:
Ciphers aes256-ctr,aes256-gcm@openssh.com,aes192-ctr,aes128-ctr,aes128-gcm@openssh.com
If this line does not contain these ciphers in exact order,
is commented out, or is missing, this is a finding.Warning:
The system needs to be rebooted for these changes to take effect. Warning:
System Crypto Modules must be provided by a vendor that undergoes
FIPS-140 certifications.
FIPS-140 is applicable to all Federal agencies that use
cryptographic-based security systems to protect sensitive information
in computer and telecommunication systems (including voice systems) as
defined in Section 5131 of the Information Technology Management Reform
Act of 1996, Public Law 104-106. This standard shall be used in
designing and implementing cryptographic modules that Federal
departments and agencies operate or are operated for them under
contract. See https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.140-2.pdf
To meet this, the system has to have cryptographic software provided by
a vendor that has undergone this certification. This means providing
documentation, test results, design information, and independent third
party review by an accredited lab. While open source software is
capable of meeting this, it does not meet FIPS-140 unless the vendor
submits to this process. Rationale:Unapproved mechanisms that are used for authentication to the cryptographic module are not verified and therefore
cannot be relied upon to provide confidentiality or integrity, and system data may be compromised.
Operating systems utilizing encryption are required to use FIPS-compliant mechanisms for authenticating to
cryptographic modules.
FIPS 140-2 is the current standard for validating that mechanisms used to access cryptographic modules
utilize authentication that meets industry and government requirements. For government systems, this allows
Security Levels 1, 2, 3, or 4 for use on Ubuntu 22.04. References:
CCI-000068, CCI-000366, CCI-000803, CCI-000877, CCI-002890, CCI-003123, SRG-OS-000033-GPOS-00014, SRG-OS-000120-GPOS-00061, SRG-OS-000125-GPOS-00065, SRG-OS-000250-GPOS-00093, SRG-OS-000393-GPOS-00173, SRG-OS-000394-GPOS-00174, UBTU-22-255050, SV-260531r958408_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-255050
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_use_approved_ciphers_ordered_stig
- name: XCCDF Value sshd_approved_ciphers # promote to variable
set_fact:
sshd_approved_ciphers: !!str aes256-ctr,aes256-gcm@openssh.com,aes192-ctr,aes128-ctr,aes128-gcm@openssh.com
tags:
- always
- name: Use Only FIPS 140-2 Validated Ciphers
block:
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)(?i)^\s*{{ "Ciphers"| regex_escape }}\s+
state: absent
- name: Check if /etc/ssh/sshd_config.d exists
stat:
path: /etc/ssh/sshd_config.d
register: _etc_ssh_sshd_config_d_exists
- name: Check if the parameter Ciphers is present in /etc/ssh/sshd_config.d
find:
paths: /etc/ssh/sshd_config.d
recurse: 'yes'
follow: 'no'
contains: (?i)^\s*{{ "Ciphers"| regex_escape }}\s+
register: _etc_ssh_sshd_config_d_has_parameter
when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir
- name: Remove parameter from files in /etc/ssh/sshd_config.d
lineinfile:
path: '{{ item.path }}'
create: false
regexp: (?i)(?i)^\s*{{ "Ciphers"| regex_escape }}\s+
state: absent
with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
when: _etc_ssh_sshd_config_d_has_parameter.matched
- name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
lineinfile:
path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
create: true
regexp: (?i)(?i)^\s*{{ "Ciphers"| regex_escape }}\s+
line: Ciphers {{ sshd_approved_ciphers }}
state: present
insertbefore: BOF
validate: /usr/sbin/sshd -t -f %s
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-255050
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_use_approved_ciphers_ordered_stig
- name: Use Only FIPS 140-2 Validated Ciphers - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
ansible.builtin.file:
path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
mode: '0600'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-255050
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_use_approved_ciphers_ordered_stig
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
sshd_approved_ciphers='aes256-ctr,aes256-gcm@openssh.com,aes192-ctr,aes128-ctr,aes128-gcm@openssh.com'
mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
LC_ALL=C sed -i "/^\s*Ciphers\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*Ciphers\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then
LC_ALL=C sed -i "/^\s*Ciphers\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
else
touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "Ciphers $sshd_approved_ciphers" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Use Only FIPS 140-2 Validated Key Exchange Algorithms
[ref]ruleLimit the key exchange algorithms to those which are FIPS-approved.
Add or modify the following line in /etc/ssh/sshd_config
KexAlgorithms ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256
This rule ensures that only the key exchange algorithms mentioned
above (or their subset) are configured for use, keeping the given
order of algorithms.Warning:
The system needs to be rebooted for these changes to take effect. Warning:
System crypto modules must be provided by a vendor that undergoes
FIPS-140 certifications.
FIPS-140 is applicable to all Federal agencies that use
cryptographic-based security systems to protect sensitive information
in computer and telecommunication systems (including voice systems) as
defined in Section 5131 of the Information Technology Management Reform
Act of 1996, Public Law 104-106. This standard shall be used in
designing and implementing cryptographic modules that Federal
departments and agencies operate or are operated for them under
contract. See https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.140-2.pdf
To meet this requirements, the system has to have cryptographic software
provided by a vendor that has undergone this certification. This means
providing documentation, test results, design information, and independent
third party review by an accredited lab. While open source software is
capable of meeting this, it does not meet FIPS-140 unless the vendor
submits to this process. Rationale:DoD information systems are required to use FIPS-approved key exchange algorithms.
The system will attempt to use the first algorithm presented by the client that matches
the server list. Listing the values "strongest to weakest" is a method to ensure the use
of the strongest algorithm available to secure the SSH connection. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-255060
- NIST-800-53-AC-17(2)
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_use_approved_kex_ordered_stig
- name: Configure sshd to use FIPS 140-2 approved key exchange algorithms
lineinfile:
path: /etc/ssh/sshd_config
line: KexAlgorithms ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256
state: present
regexp: ^\s*KexAlgorithms\s*
create: true
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-255060
- NIST-800-53-AC-17(2)
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_use_approved_kex_ordered_stig
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
KEX_ALGOS="ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,\
diffie-hellman-group-exchange-sha256"
mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
LC_ALL=C sed -i "/^\s*KexAlgorithms\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*KexAlgorithms\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then
LC_ALL=C sed -i "/^\s*KexAlgorithms\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
else
touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "KexAlgorithms $KEX_ALGOS" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Use Only FIPS 140-2 Validated MACs
[ref]ruleLimit the MACs to those hash algorithms which are FIPS-approved.
The following line in /etc/ssh/sshd_config
demonstrates use of FIPS-approved MACs:
MACs hmac-sha2-512,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-256-etm@openssh.com
If this line does not contain these MACs in exact order,
is commented out, or is missing, this is a finding.Warning:
The system needs to be rebooted for these changes to take effect. Warning:
System Crypto Modules must be provided by a vendor that undergoes
FIPS-140 certifications.
FIPS-140 is applicable to all Federal agencies that use
cryptographic-based security systems to protect sensitive information
in computer and telecommunication systems (including voice systems) as
defined in Section 5131 of the Information Technology Management Reform
Act of 1996, Public Law 104-106. This standard shall be used in
designing and implementing cryptographic modules that Federal
departments and agencies operate or are operated for them under
contract. See https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.140-2.pdf
To meet this, the system has to have cryptographic software provided by
a vendor that has undergone this certification. This means providing
documentation, test results, design information, and independent third
party review by an accredited lab. While open source software is
capable of meeting this, it does not meet FIPS-140 unless the vendor
submits to this process. Rationale:DoD Information Systems are required to use FIPS-approved cryptographic hash
functions. The only SSHv2 hash algorithms meeting this requirement is SHA2. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-255055
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_use_approved_macs_ordered_stig
- name: XCCDF Value sshd_approved_macs # promote to variable
set_fact:
sshd_approved_macs: !!str hmac-sha2-512,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-256-etm@openssh.com
tags:
- always
- name: Use Only FIPS 140-2 Validated MACs
block:
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)(?i)^\s*{{ "Macs"| regex_escape }}\s+
state: absent
- name: Check if /etc/ssh/sshd_config.d exists
stat:
path: /etc/ssh/sshd_config.d
register: _etc_ssh_sshd_config_d_exists
- name: Check if the parameter Macs is present in /etc/ssh/sshd_config.d
find:
paths: /etc/ssh/sshd_config.d
recurse: 'yes'
follow: 'no'
contains: (?i)^\s*{{ "Macs"| regex_escape }}\s+
register: _etc_ssh_sshd_config_d_has_parameter
when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir
- name: Remove parameter from files in /etc/ssh/sshd_config.d
lineinfile:
path: '{{ item.path }}'
create: false
regexp: (?i)(?i)^\s*{{ "Macs"| regex_escape }}\s+
state: absent
with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
when: _etc_ssh_sshd_config_d_has_parameter.matched
- name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
lineinfile:
path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
create: true
regexp: (?i)(?i)^\s*{{ "Macs"| regex_escape }}\s+
line: Macs {{ sshd_approved_macs }}
state: present
insertbefore: BOF
validate: /usr/sbin/sshd -t -f %s
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-255055
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_use_approved_macs_ordered_stig
- name: Use Only FIPS 140-2 Validated MACs - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
ansible.builtin.file:
path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
mode: '0600'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-255055
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_use_approved_macs_ordered_stig
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
sshd_approved_macs='hmac-sha2-512,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-256-etm@openssh.com'
mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
LC_ALL=C sed -i "/^\s*MACs\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*MACs\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then
LC_ALL=C sed -i "/^\s*MACs\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
else
touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "MACs $sshd_approved_macs" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Prevent remote hosts from connecting to the proxy display
[ref]ruleThe SSH daemon should prevent remote hosts from connecting to the proxy
display.
The default SSH configuration for X11UseLocalhost is yes ,
which prevents remote hosts from connecting to the proxy display.
To explicitly prevent remote connections to the proxy display, add or correct
the following line in
/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf :
X11UseLocalhost yes Rationale:When X11 forwarding is enabled, there may be additional exposure to the
server and client displays if the sshd proxy display is configured to listen
on the wildcard address. By default, sshd binds the forwarding server to the
loopback address and sets the hostname part of the DISPLAY
environment variable to localhost. This prevents remote hosts from
connecting to the proxy display. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-255045
- NIST-800-53-CM-6(b)
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_x11_use_localhost
- name: Prevent remote hosts from connecting to the proxy display
block:
- name: Deduplicate values from /etc/ssh/sshd_config
lineinfile:
path: /etc/ssh/sshd_config
create: false
regexp: (?i)(?i)^\s*{{ "X11UseLocalhost"| regex_escape }}\s+
state: absent
- name: Check if /etc/ssh/sshd_config.d exists
stat:
path: /etc/ssh/sshd_config.d
register: _etc_ssh_sshd_config_d_exists
- name: Check if the parameter X11UseLocalhost is present in /etc/ssh/sshd_config.d
find:
paths: /etc/ssh/sshd_config.d
recurse: 'yes'
follow: 'no'
contains: (?i)^\s*{{ "X11UseLocalhost"| regex_escape }}\s+
register: _etc_ssh_sshd_config_d_has_parameter
when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir
- name: Remove parameter from files in /etc/ssh/sshd_config.d
lineinfile:
path: '{{ item.path }}'
create: false
regexp: (?i)(?i)^\s*{{ "X11UseLocalhost"| regex_escape }}\s+
state: absent
with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
when: _etc_ssh_sshd_config_d_has_parameter.matched
- name: Insert correct line to /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
lineinfile:
path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
create: true
regexp: (?i)(?i)^\s*{{ "X11UseLocalhost"| regex_escape }}\s+
line: X11UseLocalhost yes
state: present
insertbefore: BOF
validate: /usr/sbin/sshd -t -f %s
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-255045
- NIST-800-53-CM-6(b)
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_x11_use_localhost
- name: Prevent remote hosts from connecting to the proxy display - set file mode
for /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
ansible.builtin.file:
path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
mode: '0600'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-255045
- NIST-800-53-CM-6(b)
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- sshd_x11_use_localhost
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
mkdir -p /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
chmod 0600 /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
LC_ALL=C sed -i "/^\s*X11UseLocalhost\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*X11UseLocalhost\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
if [ -e "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" ] ; then
LC_ALL=C sed -i "/^\s*X11UseLocalhost\s\+/Id" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
else
touch "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
cp "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"
# Insert at the beginning of the file
printf '%s\n' "X11UseLocalhost yes" > "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
cat "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak" >> "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
# Clean up after ourselves.
rm "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Install the OpenSSH Server Package
[ref]ruleThe openssh-server package should be installed.
The openssh-server package can be installed with the following command:
$ apt-get install openssh-server Rationale:Without protection of the transmitted information, confidentiality, and
integrity may be compromised because unprotected communications can be
intercepted and either read or altered. References:
13, 14, APO01.06, DSS05.02, DSS05.04, DSS05.07, DSS06.02, DSS06.06, CCI-002420, CCI-002421, CCI-002418, CCI-002422, SR 3.1, SR 3.8, SR 4.1, SR 4.2, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CM-6(a), PR.DS-2, PR.DS-5, FIA_UAU.5, FTP_ITC_EXT.1, FCS_SSH_EXT.1, FCS_SSHS_EXT.1, SRG-OS-000423-GPOS-00187, SRG-OS-000424-GPOS-00188, SRG-OS-000425-GPOS-00189, SRG-OS-000426-GPOS-00190, UBTU-22-255010, SV-260523r958908_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-255010
- NIST-800-53-CM-6(a)
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_openssh-server_installed
- name: Ensure openssh-server is installed
package:
name: openssh-server
state: present
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-255010
- NIST-800-53-CM-6(a)
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_openssh-server_installed
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_openssh-server
class install_openssh-server {
package { 'openssh-server':
ensure => 'installed',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "openssh-server"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[[packages]]
name = "openssh-server"
version = "*"
|
Enable the OpenSSH Service
[ref]ruleThe SSH server service, sshd, is commonly needed.
The sshd service can be enabled with the following command:
$ sudo systemctl enable sshd.service Rationale:Without protection of the transmitted information, confidentiality, and
integrity may be compromised because unprotected communications can be
intercepted and either read or altered.
This checklist item applies to both internal and external networks and all types
of information system components from which information can be transmitted (e.g., servers,
mobile devices, notebook computers, printers, copiers, scanners, etc). Communication paths
outside the physical protection of a controlled boundary are exposed to the possibility
of interception and modification. References:
13, 14, APO01.06, DSS05.02, DSS05.04, DSS05.07, DSS06.02, DSS06.06, 3.1.13, 3.5.4, 3.13.8, CCI-002420, CCI-002421, CCI-002418, CCI-002422, SR 3.1, SR 3.8, SR 4.1, SR 4.2, SR 5.2, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CM-6(a), SC-8, SC-8(1), SC-8(2), SC-8(3), SC-8(4), PR.DS-2, PR.DS-5, SRG-OS-000423-GPOS-00187, SRG-OS-000424-GPOS-00188, SRG-OS-000425-GPOS-00189, SRG-OS-000426-GPOS-00190, UBTU-22-255015, SV-260524r958908_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-255015
- NIST-800-171-3.1.13
- NIST-800-171-3.13.8
- NIST-800-171-3.5.4
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-8
- NIST-800-53-SC-8(1)
- NIST-800-53-SC-8(2)
- NIST-800-53-SC-8(3)
- NIST-800-53-SC-8(4)
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_sshd_enabled
- name: Enable the OpenSSH Service - Enable service ssh
block:
- name: Gather the package facts
package_facts:
manager: auto
- name: Enable the OpenSSH Service - Enable Service ssh
ansible.builtin.systemd:
name: ssh
enabled: true
state: started
masked: false
when:
- '"openssh-server" in ansible_facts.packages'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-255015
- NIST-800-171-3.1.13
- NIST-800-171-3.13.8
- NIST-800-171-3.5.4
- NIST-800-53-CM-6(a)
- NIST-800-53-SC-8
- NIST-800-53-SC-8(1)
- NIST-800-53-SC-8(2)
- NIST-800-53-SC-8(3)
- NIST-800-53-SC-8(4)
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_sshd_enabled
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include enable_ssh
class enable_ssh {
service {'ssh':
enable => true,
ensure => 'running',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
SYSTEMCTL_EXEC='/usr/bin/systemctl'
"$SYSTEMCTL_EXEC" unmask 'ssh.service'
if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
"$SYSTEMCTL_EXEC" start 'ssh.service'
fi
"$SYSTEMCTL_EXEC" enable 'ssh.service'
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[customizations.services]
enabled = ["ssh"]
|
System Security Services Daemon
[ref]groupThe System Security Services Daemon (SSSD) is a system daemon that provides access
to different identity and authentication providers such as Red Hat's IdM, Microsoft's AD,
openLDAP, MIT Kerberos, etc. It uses a common framework that can provide caching and offline
support to systems utilizing SSSD. SSSD using caching to reduce load on authentication
servers permit offline authentication as well as store extended user data.
For more information, see |
contains 1 rule |
Configure SSSD to Expire Offline Credentials
[ref]ruleSSSD should be configured to expire offline credentials after 1 day.
To configure SSSD to expire offline credentials, set
offline_credentials_expiration to 1 under the [pam]
section in /etc/sssd/sssd.conf . For example:
[pam]
offline_credentials_expiration = 1
Rationale:If cached authentication information is out-of-date, the validity of the
authentication information may be questionable. References:
1, 12, 15, 16, 5, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10, CCI-002007, 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4, SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3, CM-6(a), IA-5(13), PR.AC-1, PR.AC-6, PR.AC-7, SRG-OS-000383-GPOS-00166, UBTU-22-631015, SV-260581r958828_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-631015
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(13)
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- sssd_offline_cred_expiration
- name: Test for domain group
command: grep '\s*\[domain\/[^]]*]' /etc/sssd/sssd.conf
register: test_grep_domain
failed_when: false
changed_when: false
check_mode: false
when: '"sssd-common" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-631015
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(13)
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- sssd_offline_cred_expiration
- name: Add default domain group (if no domain there)
ini_file:
path: /etc/sssd/sssd.conf
section: '{{ item.section }}'
option: '{{ item.option }}'
value: '{{ item.value }}'
create: true
mode: 384
with_items:
- section: sssd
option: domains
value: default
- section: domain/default
option: id_provider
value: files
when:
- '"sssd-common" in ansible_facts.packages'
- test_grep_domain.stdout is defined
- test_grep_domain.stdout | length < 1
tags:
- DISA-STIG-UBTU-22-631015
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(13)
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- sssd_offline_cred_expiration
- name: Configure SSD to Expire Offline Credentials
ini_file:
dest: /etc/sssd/sssd.conf
section: pam
option: offline_credentials_expiration
value: 1
create: true
mode: 384
when: '"sssd-common" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-631015
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(13)
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- sssd_offline_cred_expiration
- name: Find all the conf files inside /etc/sssd/conf.d/
find:
paths: /etc/sssd/conf.d/
patterns: '*.conf'
register: sssd_conf_d_files
when: '"sssd-common" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-631015
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(13)
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- sssd_offline_cred_expiration
- name: Fix offline_credentials_expiration configuration in /etc/sssd/conf.d/
ansible.builtin.replace:
path: '{{ item.path }}'
regexp: '[^#]*offline_credentials_expiration.*'
replace: offline_credentials_expiration = 1
with_items: '{{ sssd_conf_d_files.files }}'
when: '"sssd-common" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-631015
- NIST-800-53-CM-6(a)
- NIST-800-53-IA-5(13)
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- sssd_offline_cred_expiration
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | medium |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}\n' 'sssd-common' 2>/dev/null | grep -q '^installed'; then
# sssd configuration files must be created with 600 permissions if they don't exist
# otherwise the sssd module fails to start
OLD_UMASK=$(umask)
umask u=rw,go=
found=false
# set value in all files if they contain section or key
for f in $(echo -n "/etc/sssd/sssd.conf /etc/sssd/conf.d/*.conf"); do
if [ ! -e "$f" ]; then
continue
fi
# find key in section and change value
if grep -qzosP "[[:space:]]*\[pam\]([^\n\[]*\n+)+?[[:space:]]*offline_credentials_expiration" "$f"; then
sed -i "s/offline_credentials_expiration[^(\n)]*/offline_credentials_expiration=1/" "$f"
found=true
# find section and add key = value to it
elif grep -qs "[[:space:]]*\[pam\]" "$f"; then
sed -i "/[[:space:]]*\[pam\]/a offline_credentials_expiration=1" "$f"
found=true
fi
done
# if section not in any file, append section with key = value to FIRST file in files parameter
if ! $found ; then
file=$(echo "/etc/sssd/sssd.conf /etc/sssd/conf.d/*.conf" | cut -f1 -d ' ')
mkdir -p "$(dirname "$file")"
echo -e "[pam]\noffline_credentials_expiration=1" >> "$file"
fi
umask $OLD_UMASK
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
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 Red Hat Enterprise Linux 7 Documentation available at
https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html-single/selinux_users_and_administrators_guide/index#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 88 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 72 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!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S chown,fchown,fchownat,lchown -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=unset -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!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S chown,fchown,fchownat,lchown -F auid>=1000 -F auid!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=unset -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!=unset -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!=unset -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!=unset -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!=unset -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. References:
R73, 4.1.3.9, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.5.5, 10.3.4, 10.3, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, UBTU-22-654155, SV-260633r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654155
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_chmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit chmod tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654155
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_chmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for chmod for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- chmod
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of chmod in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- chmod
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of chmod in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654155
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_chmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for chmod for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- chmod
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of chmod in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- chmod
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of chmod in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654155
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_chmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed' && { ! ( ( grep -sqE "^.*\.aarch64$" /proc/sys/kernel/osrelease || grep -sqE "^aarch64$" /proc/sys/kernel/arch; ) ); }; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="chmod"
KEY="perm_mod"
SYSCALL_GROUPING="chmod fchmod fchmodat"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
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!=unset -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!=unset -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!=unset -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!=unset -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. References:
R73, 4.1.3.9, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.5.5, 10.3.4, 10.3, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, UBTU-22-654160, SV-260634r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654160
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_chown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit chown tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654160
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_chown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for chown for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- chown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of chown in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- chown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of chown in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654160
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_chown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for chown for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- chown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of chown in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- chown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of chown in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654160
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_chown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed' && { ! ( ( grep -sqE "^.*\.aarch64$" /proc/sys/kernel/osrelease || grep -sqE "^aarch64$" /proc/sys/kernel/arch; ) ); }; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="chown"
KEY="perm_mod"
SYSCALL_GROUPING="chown fchown fchownat lchown"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
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!=unset -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!=unset -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!=unset -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!=unset -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. References:
R73, 4.1.3.9, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.5.5, 10.3.4, 10.3, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, UBTU-22-654155, SV-260633r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654155
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit fchmod tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654155
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fchmod for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fchmod
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of fchmod in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fchmod
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of fchmod in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654155
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fchmod for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fchmod
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of fchmod in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fchmod
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of fchmod in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654155
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchmod
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="fchmod"
KEY="perm_mod"
SYSCALL_GROUPING="chmod fchmod fchmodat"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
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!=unset -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!=unset -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!=unset -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!=unset -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. References:
R73, 4.1.3.9, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.5.5, 10.3.4, 10.3, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, UBTU-22-654155, SV-260633r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654155
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchmodat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit fchmodat tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654155
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchmodat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fchmodat for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fchmodat
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of fchmodat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fchmodat
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of fchmodat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654155
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchmodat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fchmodat for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fchmodat
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of fchmodat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fchmodat
syscall_grouping:
- chmod
- fchmod
- fchmodat
- name: Check existence of fchmodat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654155
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchmodat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="fchmodat"
KEY="perm_mod"
SYSCALL_GROUPING="chmod fchmod fchmodat"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
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!=unset -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!=unset -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!=unset -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!=unset -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. References:
R73, 4.1.3.9, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.5.5, 10.3.4, 10.3, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, UBTU-22-654160, SV-260634r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654160
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit fchown tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654160
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fchown for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fchown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of fchown in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fchown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of fchown in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654160
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fchown for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fchown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of fchown in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fchown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of fchown in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654160
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="fchown"
KEY="perm_mod"
SYSCALL_GROUPING="chown fchown fchownat lchown"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
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!=unset -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!=unset -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!=unset -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!=unset -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. References:
R73, 4.1.3.9, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.5.5, 10.3.4, 10.3, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, UBTU-22-654160, SV-260634r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654160
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchownat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit fchownat tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654160
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchownat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fchownat for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fchownat
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of fchownat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fchownat
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of fchownat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654160
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchownat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fchownat for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fchownat
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of fchownat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fchownat
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of fchownat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654160
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fchownat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="fchownat"
KEY="perm_mod"
SYSCALL_GROUPING="chown fchown fchownat lchown"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
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!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S fremovexattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S fremovexattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S fremovexattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S fremovexattr -F auid=0 -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. References:
R73, 4.1.3.9, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.5.5, 10.3.4, 10.3, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000466-GPOS-00210, SRG-OS-000468-GPOS-00212, SRG-OS-000064-GPOS-00033, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000499-CTR-001255, UBTU-22-654180, SV-260638r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit fremovexattr tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fremovexattr for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fremovexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fremovexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fremovexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fremovexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fremovexattr for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fremovexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fremovexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fremovexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fremovexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="fremovexattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid=0"
SYSCALL="fremovexattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
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!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S fsetxattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S fsetxattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S fsetxattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S fsetxattr -F auid=0 -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. References:
R73, 4.1.3.9, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.5.5, 10.3.4, 10.3, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000466-GPOS-00210, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000064-GPOS-00033, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270, UBTU-22-654180, SV-260638r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit fsetxattr tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fsetxattr for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fsetxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fsetxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fsetxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fsetxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for fsetxattr for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- fsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fsetxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fsetxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fsetxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- fsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of fsetxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_fsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="fsetxattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid=0"
SYSCALL="fsetxattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
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!=unset -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!=unset -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!=unset -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!=unset -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. References:
R73, 4.1.3.9, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.5.5, 10.3.4, 10.3, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, UBTU-22-654160, SV-260634r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654160
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit lchown tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654160
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for lchown for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- lchown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of lchown in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lchown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of lchown in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654160
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for lchown for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- lchown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of lchown in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lchown
syscall_grouping:
- chown
- fchown
- fchownat
- lchown
- name: Check existence of lchown in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654160
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lchown
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed' && { ! ( ( grep -sqE "^.*\.aarch64$" /proc/sys/kernel/osrelease || grep -sqE "^aarch64$" /proc/sys/kernel/arch; ) ); }; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="lchown"
KEY="perm_mod"
SYSCALL_GROUPING="chown fchown fchownat lchown"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
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!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S lremovexattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S lremovexattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S lremovexattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S lremovexattr -F auid=0 -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. References:
R73, 4.1.3.9, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.5.5, 10.3.4, 10.3, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000466-GPOS-00210, SRG-OS-000064-GPOS-00033, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270, UBTU-22-654180, SV-260638r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit lremovexattr tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for lremovexattr for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- lremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lremovexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lremovexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lremovexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lremovexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for lremovexattr for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- lremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lremovexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lremovexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lremovexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lremovexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lremovexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lremovexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="lremovexattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid=0"
SYSCALL="lremovexattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
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!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S lsetxattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S lsetxattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S lsetxattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S lsetxattr -F auid=0 -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. References:
R73, 4.1.3.9, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.5.5, 10.3.4, 10.3, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000466-GPOS-00210, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000064-GPOS-00033, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270, UBTU-22-654180, SV-260638r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit lsetxattr tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for lsetxattr for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- lsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lsetxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lsetxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lsetxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lsetxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for lsetxattr for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- lsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lsetxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lsetxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lsetxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- lsetxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of lsetxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_lsetxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="lsetxattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid=0"
SYSCALL="lsetxattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
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!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S removexattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S removexattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S removexattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S removexattr -F auid=0 -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. References:
R73, 4.1.3.9, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.5.5, 10.3.4, 10.3, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000466-GPOS-00210, SRG-OS-000064-GPOS-00033, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270, UBTU-22-654180, SV-260638r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_removexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit removexattr tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_removexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for removexattr for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- removexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of removexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- removexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of removexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- removexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of removexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- removexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of removexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_removexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for removexattr for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- removexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of removexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- removexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of removexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- removexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of removexattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- removexattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of removexattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_removexattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="removexattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid=0"
SYSCALL="removexattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
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!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S setxattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S setxattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b32 -S setxattr -F auid=0 -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!=unset -F key=perm_mod
-a always,exit -F arch=b64 -S setxattr -F auid=0 -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. References:
R73, 4.1.3.9, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.5.5, 10.3.4, 10.3, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000466-GPOS-00210, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, UBTU-22-654180, SV-260638r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_setxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit setxattr tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_setxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for setxattr for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- setxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of setxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- setxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of setxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- setxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of setxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- setxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of setxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_setxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for setxattr for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- setxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of setxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- setxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of setxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- setxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of setxattr in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- setxattr
syscall_grouping:
- fremovexattr
- lremovexattr
- removexattr
- fsetxattr
- lsetxattr
- setxattr
- name: Check existence of setxattr in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid=0 (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid=0 (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid=0 -F
key=perm_mod
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654180
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.5
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.4
- audit_rules_dac_modification_setxattr
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="setxattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid=0"
SYSCALL="setxattr"
KEY="perm_mod"
SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Execution Attempts to Run ACL Privileged Commands
[ref]groupAt a minimum, the audit system should collect the execution of
ACL privileged commands for all users and root. |
contains 2 rules |
Record Any Attempts to Run chacl
[ref]ruleAt a minimum, the audit system should collect any execution attempt
of the chacl command for all users and root. If the auditd
daemon is configured to use the augenrules program to read audit rules
during daemon startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Without generating audit records that are specific to the security and
mission needs of the organization, it would be difficult to establish,
correlate, and investigate the events relating to an incident or identify
those responsible for one.
Audit records can be generated from various components within the
information system (e.g., module or policy filter). References:
4.1.3.17, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, UBTU-22-654015, SV-260605r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654015
- audit_rules_execution_chacl
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/chacl
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chacl -F perm=x -F
auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/chacl -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/chacl -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654015
- audit_rules_execution_chacl
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/chacl -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Any Attempts to Run setfacl
[ref]ruleAt a minimum, the audit system should collect any execution attempt
of the setfacl command for all users and root. If the auditd
daemon is configured to use the augenrules program to read audit rules
during daemon startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Without generating audit records that are specific to the security and
mission needs of the organization, it would be difficult to establish,
correlate, and investigate the events relating to an incident or identify
those responsible for one.
Audit records can be generated from various components within the
information system (e.g., module or policy filter). References:
4.1.3.16, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-APP-000495-CTR-001235, UBTU-22-654085, SV-260619r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654085
- audit_rules_execution_setfacl
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/setfacl
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/setfacl -F perm=x
-F auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/setfacl -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/setfacl -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654085
- audit_rules_execution_setfacl
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/setfacl -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Execution Attempts to Run SELinux Privileged Commands
[ref]groupAt a minimum, the audit system should collect the execution of
SELinux privileged commands for all users and root. |
contains 1 rule |
Record Any Attempts to Run chcon
[ref]ruleAt a minimum, the audit system should collect any execution attempt
of the chcon command for all users and root. If the auditd
daemon is configured to use the augenrules program to read audit rules
during daemon startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset -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 threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.3.15, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270, UBTU-22-654025, SV-260607r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654025
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_execution_chcon
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/chcon
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chcon -F perm=x -F
auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/chcon -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/chcon -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654025
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_execution_chcon
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/chcon -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
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!=unset -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!=unset -F key=delete |
contains 5 rules |
Ensure auditd Collects File Deletion Events by User - rename
[ref]ruleAt a minimum, the audit system should collect file deletion events
for all users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rename -F auid>=1000 -F auid!=unset -F key=delete
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rename -F auid>=1000 -F auid!=unset -F key=delete Rationale:Auditing file deletions will create an audit trail for files that are removed
from the system. The audit trail could aid in system troubleshooting, as well as, detecting
malicious processes that attempt to delete log files to conceal their presence. References:
R73, 4.1.3.13, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.4, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.1.1, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.MA-2, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.7, 10.2.1.7, 10.2.1, 10.2, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210, SRG-OS-000467-GPOS-00211, SRG-OS-000468-GPOS-00212, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270, UBTU-22-654185, SV-260639r991577_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_rename
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit rename tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_rename
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for rename for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- rename
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of rename in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
set_fact: audit_file="/etc/audit/rules.d/delete.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- rename
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of rename in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_rename
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for rename for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- rename
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of rename in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
set_fact: audit_file="/etc/audit/rules.d/delete.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- rename
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of rename in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_rename
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed' && { ! ( ( grep -sqE "^.*\.aarch64$" /proc/sys/kernel/osrelease || grep -sqE "^aarch64$" /proc/sys/kernel/arch; ) ); }; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="rename"
KEY="delete"
SYSCALL_GROUPING="unlink unlinkat rename renameat rmdir"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects File Deletion Events by User - renameat
[ref]ruleAt a minimum, the audit system should collect file deletion events
for all users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S renameat -F auid>=1000 -F auid!=unset -F key=delete
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S renameat -F auid>=1000 -F auid!=unset -F key=delete Rationale:Auditing file deletions will create an audit trail for files that are removed
from the system. The audit trail could aid in system troubleshooting, as well as, detecting
malicious processes that attempt to delete log files to conceal their presence. References:
R73, 4.1.3.13, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.4, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.1.1, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.MA-2, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.7, 10.2.1.7, 10.2.1, 10.2, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210, SRG-OS-000467-GPOS-00211, SRG-OS-000468-GPOS-00212, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270, UBTU-22-654185, SV-260639r991577_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_renameat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit renameat tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_renameat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for renameat for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- renameat
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of renameat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
set_fact: audit_file="/etc/audit/rules.d/delete.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- renameat
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of renameat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_renameat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for renameat for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- renameat
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of renameat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
set_fact: audit_file="/etc/audit/rules.d/delete.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- renameat
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of renameat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_renameat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="renameat"
KEY="delete"
SYSCALL_GROUPING="unlink unlinkat rename renameat rmdir"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects File Deletion Events by User - rmdir
[ref]ruleAt a minimum, the audit system should collect file deletion events
for all users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rmdir -F auid>=1000 -F auid!=unset -F key=delete
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S rmdir -F auid>=1000 -F auid!=unset -F key=delete Rationale:Auditing file deletions will create an audit trail for files that are removed
from the system. The audit trail could aid in system troubleshooting, as well as, detecting
malicious processes that attempt to delete log files to conceal their presence. References:
R73, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.4, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.1.1, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.MA-2, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.7, 10.2.1.7, 10.2.1, 10.2, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210, SRG-OS-000467-GPOS-00211, SRG-OS-000468-GPOS-00212, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270, UBTU-22-654185, SV-260639r991577_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_rmdir
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit rmdir tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_rmdir
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for rmdir for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- rmdir
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of rmdir in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
set_fact: audit_file="/etc/audit/rules.d/delete.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- rmdir
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of rmdir in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_rmdir
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for rmdir for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- rmdir
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of rmdir in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
set_fact: audit_file="/etc/audit/rules.d/delete.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- rmdir
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of rmdir in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_rmdir
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed' && { ! ( ( grep -sqE "^.*\.aarch64$" /proc/sys/kernel/osrelease || grep -sqE "^aarch64$" /proc/sys/kernel/arch; ) ); }; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="rmdir"
KEY="delete"
SYSCALL_GROUPING="unlink unlinkat rename renameat rmdir"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects File Deletion Events by User - unlink
[ref]ruleAt a minimum, the audit system should collect file deletion events
for all users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S unlink -F auid>=1000 -F auid!=unset -F key=delete
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S unlink -F auid>=1000 -F auid!=unset -F key=delete Rationale:Auditing file deletions will create an audit trail for files that are removed
from the system. The audit trail could aid in system troubleshooting, as well as, detecting
malicious processes that attempt to delete log files to conceal their presence. References:
R73, 4.1.3.13, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.4, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.1.1, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.MA-2, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.7, 10.2.1.7, 10.2.1, 10.2, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210, SRG-OS-000467-GPOS-00211, SRG-OS-000468-GPOS-00212, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270, UBTU-22-654185, SV-260639r991577_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_unlink
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit unlink tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_unlink
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for unlink for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- unlink
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of unlink in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
set_fact: audit_file="/etc/audit/rules.d/delete.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- unlink
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of unlink in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_unlink
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for unlink for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- unlink
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of unlink in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
set_fact: audit_file="/etc/audit/rules.d/delete.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- unlink
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of unlink in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_unlink
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed' && { ! ( ( grep -sqE "^.*\.aarch64$" /proc/sys/kernel/osrelease || grep -sqE "^aarch64$" /proc/sys/kernel/arch; ) ); }; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="unlink"
KEY="delete"
SYSCALL_GROUPING="unlink unlinkat rename renameat rmdir"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects File Deletion Events by User - unlinkat
[ref]ruleAt a minimum, the audit system should collect file deletion events
for all users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following line to a file with suffix .rules in the
directory /etc/audit/rules.d , setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S unlinkat -F auid>=1000 -F auid!=unset -F key=delete
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file, setting ARCH to either b32 or b64 as
appropriate for your system:
-a always,exit -F arch=ARCH -S unlinkat -F auid>=1000 -F auid!=unset -F key=delete Rationale:Auditing file deletions will create an audit trail for files that are removed
from the system. The audit trail could aid in system troubleshooting, as well as, detecting
malicious processes that attempt to delete log files to conceal their presence. References:
R73, 4.1.3.13, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.4, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.1.1, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.MA-2, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.7, 10.2.1.7, 10.2.1, 10.2, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210, SRG-OS-000467-GPOS-00211, SRG-OS-000468-GPOS-00212, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270, UBTU-22-654185, SV-260639r991577_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_unlinkat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit unlinkat tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_unlinkat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for unlinkat for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- unlinkat
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of unlinkat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
set_fact: audit_file="/etc/audit/rules.d/delete.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- unlinkat
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of unlinkat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_unlinkat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for unlinkat for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- unlinkat
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of unlinkat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/delete.rules
set_fact: audit_file="/etc/audit/rules.d/delete.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- unlinkat
syscall_grouping:
- unlink
- unlinkat
- rename
- renameat
- rmdir
- name: Check existence of unlinkat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=delete
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654185
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.7
- audit_rules_file_deletion_events_unlinkat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="unlinkat"
KEY="delete"
SYSCALL_GROUPING="unlink unlinkat rename renameat rmdir"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
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!=unset -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!=unset -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!=unset -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!=unset -F key=access |
contains 6 rules |
Record Unsuccessful Access Attempts to Files - creat
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access 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:Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing
these events could serve as evidence of potential system compromise. References:
R73, 4.1.3.7, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.4, Req-10.2.1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, SRG-APP-000495-CTR-001235, UBTU-22-654165, SV-260635r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_creat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit creat tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_creat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for creat EACCES for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- creat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of creat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- creat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of creat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_creat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for creat EACCES for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- creat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of creat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- creat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of creat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_creat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for creat EPERM for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- creat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of creat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- creat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of creat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_creat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for creat EPERM for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- creat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of creat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- creat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of creat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_creat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed' && { ! ( ( grep -sqE "^.*\.aarch64$" /proc/sys/kernel/osrelease || grep -sqE "^aarch64$" /proc/sys/kernel/arch; ) ); }; then
# 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")
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="creat"
KEY="access"
SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at"
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EACCES"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EPERM"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Unsuccessful Access Attempts to Files - ftruncate
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access 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:Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing
these events could serve as evidence of potential system compromise. References:
R73, 4.1.3.7, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.4, Req-10.2.1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, SRG-APP-000495-CTR-001235, UBTU-22-654165, SV-260635r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_ftruncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit ftruncate tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_ftruncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for ftruncate EACCES for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- ftruncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of ftruncate in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- ftruncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of ftruncate in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_ftruncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for ftruncate EACCES for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- ftruncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of ftruncate in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- ftruncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of ftruncate in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_ftruncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for ftruncate EPERM for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- ftruncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of ftruncate in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- ftruncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of ftruncate in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_ftruncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for ftruncate EPERM for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- ftruncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of ftruncate in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- ftruncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of ftruncate in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_ftruncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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")
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="ftruncate"
KEY="access"
SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at"
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EACCES"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EPERM"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Unsuccessful Access Attempts to Files - open
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S open -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S open -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S open -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S open -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S open -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S open -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S open -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S open -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access 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:Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing
these events could serve as evidence of potential system compromise. References:
R73, 4.1.3.7, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.4, Req-10.2.1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, SRG-APP-000495-CTR-001235, UBTU-22-654165, SV-260635r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit open tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for open EACCES for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- open
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- open
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for open EACCES for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- open
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- open
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for open EPERM for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- open
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- open
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for open EPERM for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- open
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- open
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- not ( ansible_architecture == "aarch64" )
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed' && { ! ( ( grep -sqE "^.*\.aarch64$" /proc/sys/kernel/osrelease || grep -sqE "^aarch64$" /proc/sys/kernel/arch; ) ); }; then
# 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")
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="open"
KEY="access"
SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at"
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EACCES"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EPERM"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Unsuccessful Access Attempts to Files - open_by_handle_at
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S open_by_handle_at,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S open_by_handle_at,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S open_by_handle_at,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S open_by_handle_at,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access 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:Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing
these events could serve as evidence of potential system compromise. References:
1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.4, Req-10.2.1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, SRG-APP-000495-CTR-001235, UBTU-22-654165, SV-260635r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open_by_handle_at
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit open_by_handle_at tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open_by_handle_at
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for open_by_handle_at EACCES for 32bit
platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- open_by_handle_at
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open_by_handle_at in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- open_by_handle_at
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open_by_handle_at in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open_by_handle_at
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for open_by_handle_at EACCES for 64bit
platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- open_by_handle_at
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open_by_handle_at in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- open_by_handle_at
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open_by_handle_at in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open_by_handle_at
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for open_by_handle_at EPERM for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- open_by_handle_at
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open_by_handle_at in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- open_by_handle_at
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open_by_handle_at in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open_by_handle_at
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for open_by_handle_at EPERM for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- open_by_handle_at
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open_by_handle_at in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- open_by_handle_at
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of open_by_handle_at in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_open_by_handle_at
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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")
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="open_by_handle_at"
KEY="access"
SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at"
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EACCES"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EPERM"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Unsuccessful Access Attempts to Files - openat
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S openat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S openat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access 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:Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing
these events could serve as evidence of potential system compromise. References:
R73, 4.1.3.7, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.4, Req-10.2.1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, SRG-APP-000495-CTR-001235, UBTU-22-654165, SV-260635r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_openat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit openat tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_openat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for openat EACCES for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- openat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of openat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- openat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of openat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_openat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for openat EACCES for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- openat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of openat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- openat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of openat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_openat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for openat EPERM for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- openat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of openat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- openat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of openat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_openat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for openat EPERM for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- openat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of openat in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- openat
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of openat in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_openat
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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")
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="openat"
KEY="access"
SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at"
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EACCES"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EPERM"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Unsuccessful Access Attempts to Files - truncate
[ref]ruleAt a minimum, the audit system should collect unauthorized file
accesses for all users and root. If the auditd daemon is configured
to use the augenrules program to read audit rules during daemon
startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F arch=b32 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F arch=b32 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b32 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access
If the system is 64 bit then also add the following lines:
-a always,exit -F arch=b64 -S truncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=access
-a always,exit -F arch=b64 -S truncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=access 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:Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing
these events could serve as evidence of potential system compromise. References:
R73, 4.1.3.7, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.4, Req-10.2.1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000458-GPOS-00203, SRG-OS-000461-GPOS-00205, SRG-APP-000495-CTR-001235, UBTU-22-654165, SV-260635r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_truncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Set architecture for audit truncate tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_truncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for truncate EACCES for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- truncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of truncate in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- truncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of truncate in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_truncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for truncate EACCES for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- truncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of truncate in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- truncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of truncate in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EACCES -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EACCES -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EACCES
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_truncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for truncate EPERM for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- truncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of truncate in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- truncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of truncate in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_truncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Perform remediation of Audit rules for truncate EPERM for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- truncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of truncate in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/access.rules
set_fact: audit_file="/etc/audit/rules.d/access.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- truncate
syscall_grouping:
- creat
- ftruncate
- truncate
- open
- openat
- open_by_handle_at
- name: Check existence of truncate in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F exit=-EPERM -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F exit=-EPERM -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F exit=-EPERM
-F auid>=1000 -F auid!=unset -F key=access
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654165
- NIST-800-171-3.1.7
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.1
- PCI-DSS-Req-10.2.4
- audit_rules_unsuccessful_file_modification_truncate
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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")
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="truncate"
KEY="access"
SYSCALL_GROUPING="creat ftruncate truncate open openat open_by_handle_at"
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EACCES"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-F exit=-EPERM"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
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:
-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 3 rules |
Ensure auditd Collects Information on Kernel Module Unloading - delete_module
[ref]ruleTo capture kernel module unloading events, use following line, setting ARCH to
either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S delete_module -F auid>=1000 -F auid!=unset -F key=modules
Place to add the line depends on a way auditd daemon is configured. If it is configured
to use the augenrules program (the default), add the line to a file with suffix
.rules in the directory /etc/audit/rules.d .
If the auditd daemon is configured to use the auditctl utility,
add the line to file /etc/audit/audit.rules .Rationale:The removal of kernel modules can be used to alter the behavior of
the kernel and potentially introduce malicious code into kernel space. It is important
to have an audit trail of modules that have been introduced into the kernel. References:
R73, 4.1.3.19, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.7, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000471-GPOS-00216, SRG-OS-000477-GPOS-00222, SRG-APP-000495-CTR-001235, SRG-APP-000504-CTR-001280, UBTU-22-654170, SV-260636r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654170
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- audit_rules_kernel_module_loading_delete
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set architecture for audit delete_module tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- DISA-STIG-UBTU-22-654170
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- audit_rules_kernel_module_loading_delete
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Perform remediation of Audit rules for delete_module for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- delete_module
syscall_grouping: []
- name: Check existence of delete_module in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/module-change.rules
set_fact: audit_file="/etc/audit/rules.d/module-change.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=module-change
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- delete_module
syscall_grouping: []
- name: Check existence of delete_module in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=module-change
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654170
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- audit_rules_kernel_module_loading_delete
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Perform remediation of Audit rules for delete_module for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- delete_module
syscall_grouping: []
- name: Check existence of delete_module in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/module-change.rules
set_fact: audit_file="/etc/audit/rules.d/module-change.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=module-change
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- delete_module
syscall_grouping: []
- name: Check existence of delete_module in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=module-change
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654170
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- audit_rules_kernel_module_loading_delete
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="delete_module"
KEY="modules"
SYSCALL_GROUPING="delete_module"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on Kernel Module Loading and Unloading - finit_module
[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 to capture kernel module
loading and unloading events, setting ARCH to either b32 or b64 as appropriate for your system:
-a always,exit -F arch=ARCH -S finit_module -F auid>=1000 -F auid!=unset -F key=modules
If the auditd daemon is configured to use the auditctl utility to read audit
rules during daemon startup, add the following lines to /etc/audit/audit.rules file
in order to capture kernel module loading and unloading events, setting ARCH to either b32 or
b64 as appropriate for your system:
-a always,exit -F arch=ARCH -S finit_module -F auid>=1000 -F auid!=unset -F key=modules Rationale:The addition/removal of kernel modules can be used to alter the behavior of
the kernel and potentially introduce malicious code into kernel space. It is important
to have an audit trail of modules that have been introduced into the kernel. References:
R73, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.7, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000471-GPOS-00216, SRG-OS-000477-GPOS-00222, SRG-APP-000495-CTR-001235, SRG-APP-000504-CTR-001280, UBTU-22-654175, SV-260637r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654175
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- audit_rules_kernel_module_loading_finit
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set architecture for audit finit_module tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- DISA-STIG-UBTU-22-654175
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- audit_rules_kernel_module_loading_finit
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Perform remediation of Audit rules for finit_module for x86 platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- finit_module
syscall_grouping:
- init_module
- finit_module
- name: Check existence of finit_module in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/module-change.rules
set_fact: audit_file="/etc/audit/rules.d/module-change.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=module-change
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- finit_module
syscall_grouping:
- init_module
- finit_module
- name: Check existence of finit_module in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=module-change
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654175
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- audit_rules_kernel_module_loading_finit
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Perform remediation of Audit rules for finit_module for x86_64 platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- finit_module
syscall_grouping:
- init_module
- finit_module
- name: Check existence of finit_module in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/module-change.rules
set_fact: audit_file="/etc/audit/rules.d/module-change.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=module-change
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- finit_module
syscall_grouping:
- init_module
- finit_module
- name: Check existence of finit_module in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=module-change
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654175
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- audit_rules_kernel_module_loading_finit
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="finit_module"
KEY="modules"
SYSCALL_GROUPING="init_module finit_module"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on Kernel Module Loading - init_module
[ref]ruleTo capture kernel module loading events, use following line, setting ARCH to
either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
-a always,exit -F arch=ARCH -S init_module -F auid>=1000 -F auid!=unset -F key=modules
Place to add the line depends on a way auditd daemon is configured. If it is configured
to use the augenrules program (the default), add the line to a file with suffix
.rules in the directory /etc/audit/rules.d .
If the auditd daemon is configured to use the auditctl utility,
add the line to file /etc/audit/audit.rules .Rationale:The addition of kernel modules can be used to alter the behavior of
the kernel and potentially introduce malicious code into kernel space. It is important
to have an audit trail of modules that have been introduced into the kernel. References:
R73, 4.1.3.19, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.7, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000471-GPOS-00216, SRG-OS-000477-GPOS-00222, SRG-APP-000495-CTR-001235, SRG-APP-000504-CTR-001280, UBTU-22-654175, SV-260637r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654175
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- audit_rules_kernel_module_loading_init
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set architecture for audit init_module tasks
set_fact:
audit_arch: b64
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
== "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
tags:
- DISA-STIG-UBTU-22-654175
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- audit_rules_kernel_module_loading_init
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Perform remediation of Audit rules for init_module for 32bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- init_module
syscall_grouping:
- init_module
- finit_module
- name: Check existence of init_module in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/module-change.rules
set_fact: audit_file="/etc/audit/rules.d/module-change.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=module-change
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- init_module
syscall_grouping:
- init_module
- finit_module
- name: Check existence of init_module in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=module-change
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654175
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- audit_rules_kernel_module_loading_init
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Perform remediation of Audit rules for init_module for 64bit platform
block:
- name: Declare list of syscalls
set_fact:
syscalls:
- init_module
syscall_grouping:
- init_module
- finit_module
- name: Check existence of init_module in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/module-change.rules
set_fact: audit_file="/etc/audit/rules.d/module-change.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=module-change
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls:
- init_module
syscall_grouping:
- init_module
- finit_module
- name: Check existence of init_module in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+(( -S
|,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found |
join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k |-F
key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
-F auid!=unset -F key=module-change
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- audit_arch == "b64"
tags:
- DISA-STIG-UBTU-22-654175
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.7
- audit_rules_kernel_module_loading_init
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS=""
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL="init_module"
KEY="modules"
SYSCALL_GROUPING="init_module finit_module"
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Attempts to Alter Logon and Logout Events
[ref]groupThe audit system already collects login information for all users
and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing logon events:
-w /var/log/tallylog -p wa -k logins
-w /var/log/faillock -p wa -k logins
-w /var/log/lastlog -p wa -k logins
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for unattempted manual
edits of files involved in storing logon events:
-w /var/log/tallylog -p wa -k logins
-w /var/log/faillock -p wa -k logins
-w /var/log/lastlog -p wa -k logins |
contains 2 rules |
Record Attempts to Alter Logon and Logout Events - faillog
[ref]ruleThe audit system already collects login information for all users
and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing logon events:
-w /var/log/faillog -p wa -k logins
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for unattempted manual
edits of files involved in storing logon events:
-w /var/log/faillog -p wa -k logins Rationale:Manual editing of these files may indicate nefarious activity, such
as an attacker attempting to remove evidence of an intrusion. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654210
- audit_rules_login_events_faillog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /var/log/faillog already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/var/log/faillog\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654210
- audit_rules_login_events_faillog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key logins
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)logins$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-22-654210
- audit_rules_login_events_faillog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use /etc/audit/rules.d/logins.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/logins.rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-22-654210
- audit_rules_login_events_faillog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-22-654210
- audit_rules_login_events_faillog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /var/log/faillog in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /var/log/faillog -p wa -k logins
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-22-654210
- audit_rules_login_events_faillog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /var/log/faillog already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/var/log/faillog\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654210
- audit_rules_login_events_faillog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /var/log/faillog in /etc/audit/audit.rules
lineinfile:
line: -w /var/log/faillog -p wa -k logins
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- DISA-STIG-UBTU-22-654210
- audit_rules_login_events_faillog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# 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]+/var/log/faillog" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/var/log/faillog $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 "wa" | 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\+/var/log/faillog$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 /var/log/faillog -p wa -k logins" >> "$audit_rules_file"
fi
done
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# 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/logins.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/faillog" /etc/audit/rules.d/*.rules)
# 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+=("$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/logins.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/logins.rules"
# If the logins.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0600 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
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]+/var/log/faillog" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/var/log/faillog $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 "wa" | 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\+/var/log/faillog$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 /var/log/faillog -p wa -k logins" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Attempts to Alter Logon and Logout Events - lastlog
[ref]ruleThe audit system already collects login information for all users
and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing logon events:
-w /var/log/lastlog -p wa -k logins
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for unattempted manual
edits of files involved in storing logon events:
-w /var/log/lastlog -p wa -k logins Rationale:Manual editing of these files may indicate nefarious activity, such
as an attacker attempting to remove evidence of an intrusion. References:
R73, 4.1.3.12, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.3, 10.2.1.3, 10.2.1, 10.2, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000473-GPOS-00218, SRG-OS-000470-GPOS-00214, SRG-APP-000495-CTR-001235, SRG-APP-000503-CTR-001275, SRG-APP-000506-CTR-001290, UBTU-22-654215, SV-260645r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654215
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.3
- audit_rules_login_events_lastlog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /var/log/lastlog already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/var/log/lastlog\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654215
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.3
- audit_rules_login_events_lastlog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key logins
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)logins$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-22-654215
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.3
- audit_rules_login_events_lastlog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use /etc/audit/rules.d/logins.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/logins.rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-22-654215
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.3
- audit_rules_login_events_lastlog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-22-654215
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.3
- audit_rules_login_events_lastlog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /var/log/lastlog in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /var/log/lastlog -p wa -k logins
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-22-654215
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.3
- audit_rules_login_events_lastlog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /var/log/lastlog already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/var/log/lastlog\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654215
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.3
- audit_rules_login_events_lastlog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /var/log/lastlog in /etc/audit/audit.rules
lineinfile:
line: -w /var/log/lastlog -p wa -k logins
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- DISA-STIG-UBTU-22-654215
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.3
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.3
- audit_rules_login_events_lastlog
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# 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]+/var/log/lastlog" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/var/log/lastlog $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 "wa" | 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\+/var/log/lastlog$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 /var/log/lastlog -p wa -k logins" >> "$audit_rules_file"
fi
done
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# 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/logins.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/lastlog" /etc/audit/rules.d/*.rules)
# 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+=("$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/logins.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/logins.rules"
# If the logins.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0600 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
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]+/var/log/lastlog" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/var/log/lastlog $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 "wa" | 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\+/var/log/lastlog$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 /var/log/lastlog -p wa -k logins" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
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 21 rules |
Record Any Attempts to Run apparmor_parser
[ref]ruleAt a minimum, the audit system should collect any execution attempt
of the apparmor_parser command for all users and root. If
the auditd daemon is configured to use the augenrules
program to read audit rules during daemon startup (the default), add
the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d :
-a always,exit -F path=/sbin/apparmor_parser -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following
lines to /etc/audit/audit.rules file:
-a always,exit -F path=/sbin/apparmor_parser -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Without generating audit records that are specific to the security and
mission needs of the organization, it would be difficult to establish,
correlate, and investigate the events relating to an incident or identify
those responsible for one.
Audit records can be generated from various components within the
information system (e.g., module or policy filter). Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654010
- audit_rules_privileged_commands_apparmor_parser
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /sbin/apparmor_parser
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/sbin/apparmor_parser -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/sbin/apparmor_parser -F perm=x
-F auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/sbin/apparmor_parser
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/sbin/apparmor_parser -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/sbin/apparmor_parser -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/sbin/apparmor_parser
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654010
- audit_rules_privileged_commands_apparmor_parser
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/sbin/apparmor_parser -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - chage
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=unset -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 threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.3.6, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-APP-000029-CTR-000085, SRG-APP-000495-CTR-001235, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270, UBTU-22-654020, SV-260606r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654020
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_chage
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/chage
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chage -F perm=x -F
auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/chage -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/chage -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654020
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_chage
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/chage -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - chfn
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/chfn -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/chfn -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged Rationale:Without generating audit records that are specific to the security and
mission needs of the organization, it would be difficult to establish,
correlate, and investigate the events relating to an incident or identify
those responsible for one.
Audit records can be generated from various components within the
information system (e.g., module or policy filter). References:
4.1.3.6, CCI-000130, CCI-000169, CCI-000172, CCI-002884, AU-3, AU-12(a), AU-12(c), MA-4(1)(a), UBTU-22-654030, SV-260608r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654030
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-3
- NIST-800-53-MA-4(1)(a)
- audit_rules_privileged_commands_chfn
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/chfn
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chfn -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chfn -F perm=x -F
auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/chfn -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chfn -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/chfn -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/chfn -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654030
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-3
- NIST-800-53-MA-4(1)(a)
- audit_rules_privileged_commands_chfn
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/chfn -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - chsh
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=unset -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 threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.3.6, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-APP-000495-CTR-001235, UBTU-22-654035, SV-260609r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654035
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_chsh
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/chsh
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chsh -F perm=x -F
auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/chsh -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/chsh -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654035
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_chsh
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/chsh -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - crontab
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=unset -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 threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.3.6, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-APP-000495-CTR-001235, UBTU-22-654040, SV-260610r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654040
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_crontab
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/crontab
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/crontab -F perm=x
-F auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/crontab -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/crontab -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654040
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_crontab
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/crontab -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - fdisk
[ref]ruleConfigure the operating system to audit the execution of the partition
management program "fdisk". Rationale:Without generating audit records that are specific to the security
and mission needs of the organization, it would be difficult to
establish, correlate, and investigate the events relating to an
incident or identify those responsible for one.
Audit records can be generated from various components within the
information system (e.g., module or policy filter). Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654045
- audit_rules_privileged_commands_fdisk
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Check if watch rule for /sbin/fdisk already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/sbin/fdisk\s+-p\s+x(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654045
- audit_rules_privileged_commands_fdisk
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key modules
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)modules$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-22-654045
- audit_rules_privileged_commands_fdisk
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Use /etc/audit/rules.d/modules.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/modules.rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-22-654045
- audit_rules_privileged_commands_fdisk
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-22-654045
- audit_rules_privileged_commands_fdisk
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Add watch rule for /sbin/fdisk in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /sbin/fdisk -p x -k modules
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-22-654045
- audit_rules_privileged_commands_fdisk
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Check if watch rule for /sbin/fdisk already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/sbin/fdisk\s+-p\s+x(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654045
- audit_rules_privileged_commands_fdisk
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Add watch rule for /sbin/fdisk in /etc/audit/audit.rules
lineinfile:
line: -w /sbin/fdisk -p x -k modules
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- DISA-STIG-UBTU-22-654045
- audit_rules_privileged_commands_fdisk
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# 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]+/sbin/fdisk" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/sbin/fdisk $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 "x" | 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\+/sbin/fdisk$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 /sbin/fdisk -p x -k modules" >> "$audit_rules_file"
fi
done
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# 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/modules.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/sbin/fdisk" /etc/audit/rules.d/*.rules)
# 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+=("$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/modules.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/modules.rules"
# If the modules.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0600 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
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]+/sbin/fdisk" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/sbin/fdisk $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 "x" | 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\+/sbin/fdisk$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 /sbin/fdisk -p x -k modules" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - gpasswd
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=unset -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 threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.3.6, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-APP-000029-CTR-000085, SRG-APP-000495-CTR-001235, UBTU-22-654050, SV-260612r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654050
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_gpasswd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/gpasswd
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/gpasswd -F perm=x
-F auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/gpasswd -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/gpasswd -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654050
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_gpasswd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/gpasswd -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - kmod
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-w /usr/bin/kmod -p x -k modules
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 :
-w /usr/bin/kmod -p x -k modules Rationale:Without generating audit records that are specific to the security and
mission needs of the organization, it would be difficult to establish,
correlate, and investigate the events relating to an incident or identify
those responsible for one.
Audit records can be generated from various components within the
information system (e.g., module or policy filter). References:
R73, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, AU-3, AU-3.1, AU-12(a), AU-12.1(ii), AU-12.1(iv)AU-12(c), MA-4(1)(a), SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000471-GPOS-00216, SRG-OS-000477-GPOS-00222, SRG-APP-000495-CTR-001235, SRG-APP-000504-CTR-001280, UBTU-22-654055, SV-260613r991586_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654055
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)AU-12(c)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- audit_rules_privileged_commands_kmod
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/kmod
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/kmod -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/kmod -F perm=x -F
auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/kmod -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/kmod -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/kmod -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/kmod -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654055
- NIST-800-53-AU-12(a)
- NIST-800-53-AU-12.1(ii)
- NIST-800-53-AU-12.1(iv)AU-12(c)
- NIST-800-53-AU-3
- NIST-800-53-AU-3.1
- NIST-800-53-MA-4(1)(a)
- audit_rules_privileged_commands_kmod
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/kmod -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - modprobe
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-w /sbin/modprobe -p x -k modules
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 :
-w /sbin/modprobe -p x -k modules 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 threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
R73, 4.1.3.19, CCI-000130, CCI-000169, CCI-000172, CCI-002884, AU-12(a), AU-12.1(ii), AU-3, AU-3.1, AU-12(c), AU-12.1(iv), MA-4(1)(a), SRG-OS-000037-GPOS-00015, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, UBTU-22-654060, SV-260614r991586_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# 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]+/sbin/modprobe" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/sbin/modprobe $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 "x" | 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\+/sbin/modprobe$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 /sbin/modprobe -p x -k modules" >> "$audit_rules_file"
fi
done
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# 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/modules.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/sbin/modprobe" /etc/audit/rules.d/*.rules)
# 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+=("$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/modules.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/modules.rules"
# If the modules.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0600 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
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]+/sbin/modprobe" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/sbin/modprobe $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 "x" | 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\+/sbin/modprobe$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 /sbin/modprobe -p x -k modules" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - mount
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=unset -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 threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.3.6, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, AU-2(d), AU-12(c), AC-6(9), CM-6(a), SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-APP-000029-CTR-000085, UBTU-22-654065, SV-260615r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654065
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_mount
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/mount
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/mount -F perm=x -F
auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/mount -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/mount -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654065
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_mount
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/mount -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - newgrp
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=unset -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 threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.3.6, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-APP-000029-CTR-000085, SRG-APP-000495-CTR-001235, UBTU-22-654070, SV-260616r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654070
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_newgrp
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/newgrp
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/newgrp -F perm=x -F
auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/newgrp -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/newgrp -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654070
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_newgrp
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/newgrp -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - pam_timestamp_check
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/sbin/pam_timestamp_check
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/sbin/pam_timestamp_check
-F perm=x -F auid>=1000 -F auid!=unset -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 threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-APP-000029-CTR-000085, SRG-APP-000495-CTR-001235, UBTU-22-654075, SV-260617r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654075
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_pam_timestamp_check
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/sbin/pam_timestamp_check
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/sbin/pam_timestamp_check -F perm=x -F auid>=1000 -F auid!=unset
(-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/pam_timestamp_check
-F perm=x -F auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/sbin/pam_timestamp_check
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/sbin/pam_timestamp_check -F perm=x -F auid>=1000 -F auid!=unset
(-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/sbin/pam_timestamp_check -F perm=x -F auid>=1000
-F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/sbin/pam_timestamp_check
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654075
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_pam_timestamp_check
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/sbin/pam_timestamp_check -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - passwd
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=unset -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 threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-APP-000029-CTR-000085, SRG-APP-000495-CTR-001235, UBTU-22-654080, SV-260618r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654080
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_passwd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/passwd
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/passwd -F perm=x -F
auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/passwd -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/passwd -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654080
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_passwd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/passwd -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Any Attempts to Run ssh-agent
[ref]ruleAt a minimum, the audit system should collect any execution attempt
of the ssh-agent command for all users and root. If the auditd
daemon is configured to use the augenrules program to read audit rules
during daemon startup (the default), add the following lines to a file with suffix
.rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/ssh-agent -F perm=x -F auid>=1000 -F auid!=unset -k privileged-ssh-agent
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file:
-a always,exit -F path=/usr/bin/ssh-agent -F perm=x -F auid>=1000 -F auid!=unset -k privileged-ssh-agent Rationale:Without generating audit records that are specific to the security and
mission needs of the organization, it would be difficult to establish,
correlate, and investigate the events relating to an incident or identify
those responsible for one.
Audit records can be generated from various components within the
information system (e.g., module or policy filter). References:
4.1.3.6, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-APP-000495-CTR-001235, UBTU-22-654090, SV-260620r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654090
- audit_rules_privileged_commands_ssh_agent
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/ssh-agent
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/ssh-agent -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/ssh-agent -F perm=x
-F auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/ssh-agent -F
perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/ssh-agent -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/ssh-agent -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/ssh-agent -F
perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654090
- audit_rules_privileged_commands_ssh_agent
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/ssh-agent -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - ssh-keysign
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/lib/openssh/ssh-keysign -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/lib/openssh/ssh-keysign -F perm=x -F auid>=1000 -F auid!=unset -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 threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.3.6, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-APP-000029-CTR-000085, SRG-APP-000495-CTR-001235, UBTU-22-654095, SV-260621r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654095
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_ssh_keysign
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/lib/openssh/ssh-keysign
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/lib/openssh/ssh-keysign -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/lib/openssh/ssh-keysign
-F perm=x -F auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/lib/openssh/ssh-keysign
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/lib/openssh/ssh-keysign -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/lib/openssh/ssh-keysign -F perm=x -F auid>=1000
-F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/lib/openssh/ssh-keysign
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654095
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_ssh_keysign
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/lib/openssh/ssh-keysign -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - su
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=unset -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 threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.3.6, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000064-GPOS-0003, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210, SRG-APP-000029-CTR-000085, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-OS-000755-GPOS-00220, UBTU-22-654100, SV-260622r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654100
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_su
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/su
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/su -F perm=x -F auid>=1000
-F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/su -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=unset (?:-k
|-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/su -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654100
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_su
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/su -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - sudo
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=unset -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 threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
R33, 4.1.3.6, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210, SRG-APP-000029-CTR-000085, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-OS-000755-GPOS-00220, UBTU-22-654105, SV-260623r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654105
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_sudo
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/sudo
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/sudo -F perm=x -F
auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/sudo -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/sudo -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654105
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_sudo
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/sudo -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - sudoedit
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/sudoedit -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/sudoedit -F perm=x -F auid>=1000 -F auid!=unset -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 threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.3.6, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-APP-000495-CTR-001235, SRG-OS-000755-GPOS-00220, UBTU-22-654110, SV-260624r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654110
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_sudoedit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/sudoedit
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/sudoedit -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/sudoedit -F perm=x
-F auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/sudoedit -F
perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/sudoedit -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/sudoedit -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/sudoedit -F
perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654110
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_sudoedit
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/sudoedit -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - umount
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=unset -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 threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.3.6, 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-000130, CCI-000169, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-APP-000029-CTR-000085, UBTU-22-654115, SV-260625r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654115
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_umount
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/bin/umount
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/umount -F perm=x -F
auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/umount -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/umount -F perm=x
-F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654115
- NIST-800-171-3.1.7
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- audit_rules_privileged_commands_umount
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/bin/umount -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - unix_update
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/sbin/unix_update -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/sbin/unix_update -F perm=x -F auid>=1000 -F auid!=unset -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 threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-APP-000495-CTR-001235, UBTU-22-654120, SV-260626r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654120
- audit_rules_privileged_commands_unix_update
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/sbin/unix_update
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/sbin/unix_update -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/unix_update -F perm=x
-F auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/sbin/unix_update
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/sbin/unix_update -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/sbin/unix_update -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/sbin/unix_update
-F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654120
- audit_rules_privileged_commands_unix_update
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/sbin/unix_update -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects Information on the Use of Privileged Commands - usermod
[ref]ruleAt a minimum, the audit system should collect the execution of
privileged commands for all users and root. If the auditd daemon is
configured to use the augenrules program to read audit rules during
daemon startup (the default), add a line of the following form to a file with
suffix .rules in the directory /etc/audit/rules.d :
-a always,exit -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add a line of the following
form to /etc/audit/audit.rules :
-a always,exit -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset -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 threats.
Privileged programs are subject to escalation-of-privilege attacks,
which attempt to subvert their normal role of providing some necessary but
limited capability. As such, motivation exists to monitor these programs for
unusual activity. References:
4.1.3.18, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000466-GPOS-00210, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, UBTU-22-654125, SV-260627r958446_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654125
- audit_rules_privileged_commands_usermod
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Perform remediation of Audit rules for /usr/sbin/usermod
block:
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: '*.rules'
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Reset syscalls found per file
set_fact:
syscalls_per_file: {}
found_paths_dict: {}
- name: Declare syscalls found per file
set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
:[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
loop: '{{ find_command.results | selectattr(''matched'') | list }}'
- name: Declare files where syscalls were found
set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
| map(attribute='path') | list }}"
- name: Count occurrences of syscalls in paths
set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
0) }) }}"
loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
| list }}'
- name: Get path with most syscalls
set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
| last).key }}"
when: found_paths | length >= 1
- name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
when: found_paths | length == 0
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
| join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/usermod -F perm=x
-F auid>=1000 -F auid!=unset (?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/sbin/usermod -F
perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
- name: Declare list of syscalls
set_fact:
syscalls: []
syscall_grouping: []
- name: Check existence of in /etc/audit/audit.rules
find:
paths: /etc/audit
contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)* -F
path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
patterns: audit.rules
register: find_command
loop: '{{ (syscall_grouping + syscalls) | unique }}'
- name: Set path to /etc/audit/audit.rules
set_fact: audit_file="/etc/audit/audit.rules"
- name: Declare found syscalls
set_fact: syscalls_found="{{ find_command.results | selectattr('matched') | map(attribute='item')
| list }}"
- name: Declare missing syscalls
set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"
- name: Replace the audit rule in {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|") }}))\b)((?:(
-S |,)\w+)+)( -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset
(?:-k |-F key=)\w+)
line: \1\2\3{{ missing_syscalls | join("\3") }}\4
backrefs: true
state: present
mode: g-rwx,o-rwx
when: syscalls_found | length > 0 and missing_syscalls | length > 0
- name: Add the audit rule to {{ audit_file }}
lineinfile:
path: '{{ audit_file }}'
line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/sbin/usermod -F
perm=x -F auid>=1000 -F auid!=unset -F key=privileged
create: true
mode: g-rwx,o-rwx
state: present
when: syscalls_found | length == 0
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654125
- audit_rules_privileged_commands_usermod
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
ACTION_ARCH_FILTERS="-a always,exit"
OTHER_FILTERS="-F path=/usr/sbin/usermod -F perm=x"
AUID_FILTERS="-F auid>=1000 -F auid!=unset"
SYSCALL=""
KEY="privileged"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Attempts to Alter Process and Session Initiation Information btmp
[ref]ruleThe audit system already collects process information for all
users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing such process information:
-w /var/log/btmp -p wa -k session
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for attempted manual
edits of files involved in storing such process information:
-w /var/log/btmp -p wa -k session Rationale:Manual editing of these files may indicate nefarious activity, such
as an attacker attempting to remove evidence of an intrusion. Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# 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]+/var/log/btmp" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/var/log/btmp $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 "wa" | 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\+/var/log/btmp$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 /var/log/btmp -p wa -k session" >> "$audit_rules_file"
fi
done
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# 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/session.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/btmp" /etc/audit/rules.d/*.rules)
# 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+=("$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/session.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/session.rules"
# If the session.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0600 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
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]+/var/log/btmp" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/var/log/btmp $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 "wa" | 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\+/var/log/btmp$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 /var/log/btmp -p wa -k session" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Attempts to Alter Process and Session Initiation Information utmp
[ref]ruleThe audit system already collects process information for all
users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing such process information:
-w /run/utmp -p wa -k session
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for attempted manual
edits of files involved in storing such process information:
-w /run/utmp -p wa -k session Rationale:Manual editing of these files may indicate nefarious activity, such
as an attacker attempting to remove evidence of an intrusion. Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# 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]+/run/utmp" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/run/utmp $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 "wa" | 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\+/run/utmp$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 /run/utmp -p wa -k session" >> "$audit_rules_file"
fi
done
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# 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/session.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/run/utmp" /etc/audit/rules.d/*.rules)
# 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+=("$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/session.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/session.rules"
# If the session.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0600 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
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]+/run/utmp" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/run/utmp $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 "wa" | 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\+/run/utmp$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 /run/utmp -p wa -k session" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Attempts to Alter Process and Session Initiation Information wtmp
[ref]ruleThe audit system already collects process information for all
users and root. If the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d in order to watch for attempted manual
edits of files involved in storing such process information:
-w /var/log/wtmp -p wa -k session
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file in order to watch for attempted manual
edits of files involved in storing such process information:
-w /var/log/wtmp -p wa -k session Rationale:Manual editing of these files may indicate nefarious activity, such
as an attacker attempting to remove evidence of an intrusion. Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# 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]+/var/log/wtmp" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/var/log/wtmp $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 "wa" | 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\+/var/log/wtmp$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 /var/log/wtmp -p wa -k session" >> "$audit_rules_file"
fi
done
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# 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/session.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/wtmp" /etc/audit/rules.d/*.rules)
# 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+=("$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/session.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/session.rules"
# If the session.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0600 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
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]+/var/log/wtmp" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/var/log/wtmp $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 "wa" | 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\+/var/log/wtmp$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 /var/log/wtmp -p wa -k session" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects System Administrator Actions - /etc/sudoers
[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
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following line to
/etc/audit/audit.rules file:
-w /etc/sudoers -p wa -k actions Rationale:The actions taken by system administrators should be audited to keep a record
of what was executed on the system, as well as, for accountability purposes.
Editing the sudoers file may be sign of an attacker trying to
establish persistent methods to a system, auditing the editing of the sudoers
files mitigates this risk. References:
CCI-001403, CCI-001404, CCI-001405, CCI-000172, CCI-000130, CCI-002130, CCI-000135, CCI-000169, CCI-002884, CCI-000018, CCI-000015, SRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000503-CTR-001275, UBTU-22-654220, SV-260646r991575_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654220
- audit_rules_sudoers
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Check if watch rule for /etc/sudoers already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/etc/sudoers\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654220
- audit_rules_sudoers
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key actions
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)actions$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-22-654220
- audit_rules_sudoers
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Use /etc/audit/rules.d/actions.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/actions.rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-22-654220
- audit_rules_sudoers
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-22-654220
- audit_rules_sudoers
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Add watch rule for /etc/sudoers in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /etc/sudoers -p wa -k actions
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-22-654220
- audit_rules_sudoers
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Check if watch rule for /etc/sudoers already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/etc/sudoers\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654220
- audit_rules_sudoers
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Add watch rule for /etc/sudoers in /etc/audit/audit.rules
lineinfile:
line: -w /etc/sudoers -p wa -k actions
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- DISA-STIG-UBTU-22-654220
- audit_rules_sudoers
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# 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]+/etc/sudoers" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/etc/sudoers $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 "wa" | 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\+/etc/sudoers$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 /etc/sudoers -p wa -k actions" >> "$audit_rules_file"
fi
done
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# 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/actions.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/sudoers" /etc/audit/rules.d/*.rules)
# 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+=("$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/actions.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/actions.rules"
# If the actions.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0600 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
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]+/etc/sudoers" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/etc/sudoers $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 "wa" | 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\+/etc/sudoers$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 /etc/sudoers -p wa -k actions" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects System Administrator Actions - /etc/sudoers.d/
[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.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.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.
Editing the sudoers file may be sign of an attacker trying to
establish persistent methods to a system, auditing the editing of the sudoers
files mitigates this risk. References:
CCI-001403, CCI-001404, CCI-001405, CCI-000172, CCI-000130, CCI-002130, CCI-000135, CCI-000169, CCI-002884, CCI-000018, CCI-000015, SRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000503-CTR-001275, UBTU-22-654225, SV-260647r991575_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654225
- audit_rules_sudoers_d
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Check if watch rule for /etc/sudoers.d/ already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/etc/sudoers.d/\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654225
- audit_rules_sudoers_d
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key actions
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)actions$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-22-654225
- audit_rules_sudoers_d
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Use /etc/audit/rules.d/actions.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/actions.rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-22-654225
- audit_rules_sudoers_d
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-22-654225
- audit_rules_sudoers_d
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Add watch rule for /etc/sudoers.d/ in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /etc/sudoers.d/ -p wa -k actions
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-22-654225
- audit_rules_sudoers_d
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Check if watch rule for /etc/sudoers.d/ already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/etc/sudoers.d/\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654225
- audit_rules_sudoers_d
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Add watch rule for /etc/sudoers.d/ in /etc/audit/audit.rules
lineinfile:
line: -w /etc/sudoers.d/ -p wa -k actions
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- DISA-STIG-UBTU-22-654225
- audit_rules_sudoers_d
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# 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]+/etc/sudoers.d/" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/etc/sudoers.d/ $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 "wa" | 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\+/etc/sudoers.d/$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 /etc/sudoers.d/ -p wa -k actions" >> "$audit_rules_file"
fi
done
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# 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/actions.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/sudoers.d/" /etc/audit/rules.d/*.rules)
# 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+=("$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/actions.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/actions.rules"
# If the actions.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0600 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
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]+/etc/sudoers.d/" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/etc/sudoers.d/ $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 "wa" | 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\+/etc/sudoers.d/$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 /etc/sudoers.d/ -p wa -k actions" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Events When Privileged Executables Are Run
[ref]ruleVerify the system generates an audit record when privileged functions are executed.
If audit is using the "auditctl" tool to load the rules, run the following command:
$ sudo grep execve /etc/audit/audit.rules
If audit is using the "augenrules" tool to load the rules, run the following command:
$ sudo grep -r execve /etc/audit/rules.d
-a always,exit -F arch=b32 -S execve -C uid!=euid -F euid=0 -k setuid
-a always,exit -F arch=b64 -S execve -C uid!=euid -F euid=0 -k setuid
-a always,exit -F arch=b32 -S execve -C gid!=egid -F egid=0 -k setgid
-a always,exit -F arch=b64 -S execve -C gid!=egid -F egid=0 -k setgid
If both the "b32" and "b64" audit rules for "SUID" files are not defined, this is a finding.
If both the "b32" and "b64" audit rules for "SGID" files are not defined, this is a finding.Warning:
Note that these rules can be configured in a
number of ways while still achieving the desired effect. Rationale:Misuse of privileged functions, either intentionally or unintentionally by
authorized users, or by unauthorized external entities that have
compromised information 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 threats and the advanced persistent threat. References:
4.1.3.2, CCI-002233, CCI-002234, CM-5(1), AU-7(a), AU-7(b), AU-8(b), AU-12(3), AC-6(9), 10.2.1.2, 10.2.1, 10.2, SRG-OS-000326-GPOS-00126, SRG-OS-000327-GPOS-00127, SRG-APP-000343-CTR-000780, SRG-APP-000381-CTR-000905, SRG-OS-000755-GPOS-00220, UBTU-22-654230, SV-260648r958730_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-C uid!=euid -F euid=0"
AUID_FILTERS=""
SYSCALL="execve"
KEY="setuid"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
for ARCH in "${RULE_ARCHS[@]}"
do
ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
OTHER_FILTERS="-C gid!=egid -F egid=0"
AUID_FILTERS=""
SYSCALL="execve"
KEY="setgid"
SYSCALL_GROUPING=""
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# 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
default_file="/etc/audit/rules.d/$KEY.rules"
# As other_filters may include paths, lets use a different delimiter for it
# The "F" script expression tells sed to print the filenames where the expressions matched
readarray -t files_to_inspect < <(sed -s -n -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
if [ ${#files_to_inspect[@]} -eq "0" ]
then
file_to_inspect="/etc/audit/rules.d/$KEY.rules"
files_to_inspect=("$file_to_inspect")
if [ ! -e "$file_to_inspect" ]
then
touch "$file_to_inspect"
chmod 0600 "$file_to_inspect"
fi
fi
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
unset syscall_a
unset syscall_grouping
unset syscall_string
unset syscall
unset file_to_edit
unset rule_to_edit
unset rule_syscalls_to_edit
unset other_string
unset auid_string
unset full_rule
# Load macro arguments into arrays
read -a syscall_a <<< $SYSCALL
read -a syscall_grouping <<< $SYSCALL_GROUPING
# 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 |
# -----------------------------------------------------------------------------------------
#
files_to_inspect=()
# If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# file to the list of files to be inspected
default_file="/etc/audit/audit.rules"
files_to_inspect+=('/etc/audit/audit.rules' )
# After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
skip=1
for audit_file in "${files_to_inspect[@]}"
do
# Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
# i.e, collect rules that match:
# * the action, list and arch, (2-nd argument)
# * the other filters, (3-rd argument)
# * the auid filters, (4-rd argument)
readarray -t similar_rules < <(sed -e "/^$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")
candidate_rules=()
# Filter out rules that have more fields then required. This will remove rules more specific than the required scope
for s_rule in "${similar_rules[@]}"
do
# Strip all the options and fields we know of,
# than check if there was any field left over
extra_fields=$(sed -E -e "s/^$ACTION_ARCH_FILTERS//" -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
done
if [[ ${#syscall_a[@]} -ge 1 ]]
then
# Check if the syscall we want is present in any of the similar existing rules
for rule in "${candidate_rules[@]}"
do
rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
all_syscalls_found=0
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
# A syscall was not found in the candidate rule
all_syscalls_found=1
}
done
if [[ $all_syscalls_found -eq 0 ]]
then
# We found a rule with all the syscall(s) we want; skip rest of macro
skip=0
break
fi
# Check if this rule can be grouped with our target syscall and keep track of it
for syscall_g in "${syscall_grouping[@]}"
do
if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
then
file_to_edit=${audit_file}
rule_to_edit=${rule}
rule_syscalls_to_edit=${rule_syscalls}
fi
done
done
else
# If there is any candidate rule, it is compliant; skip rest of macro
if [ "${#candidate_rules[@]}" -gt 0 ]
then
skip=0
fi
fi
if [ "$skip" -eq 0 ]; then
break
fi
done
if [ "$skip" -ne 0 ]; then
# We checked all rules that matched the expected resemblance pattern (action, arch & auid)
# At this point we know if we need to either append the $full_rule or group
# the syscall together with an exsiting rule
# Append the full_rule if it cannot be grouped to any other rule
if [ -z ${rule_to_edit+x} ]
then
# Build full_rule while avoid adding double spaces when other_filters is empty
if [ "${#syscall_a[@]}" -gt 0 ]
then
syscall_string=""
for syscall in "${syscall_a[@]}"
do
syscall_string+=" -S $syscall"
done
fi
other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
echo "$full_rule" >> "$default_file"
chmod 0600 ${default_file}
else
# Check if the syscalls are declared as a comma separated list or
# as multiple -S parameters
if grep -q -- "," <<< "${rule_syscalls_to_edit}"
then
delimiter=","
else
delimiter=" -S "
fi
new_grouped_syscalls="${rule_syscalls_to_edit}"
for syscall in "${syscall_a[@]}"
do
grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
# A syscall was not found in the candidate rule
new_grouped_syscalls+="${delimiter}${syscall}"
}
done
# Group the syscall in the rule
sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
fi
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Events that Modify User/Group Information - /etc/group
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d , in order to capture events that modify
account changes:
-w /etc/group -p wa -k audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file, in order to capture events that modify
account changes:
-w /etc/group -p wa -k audit_rules_usergroup_modification Rationale:In addition to auditing new user and group accounts, these watches
will alert the system administrator(s) to any modifications. Any unexpected
users, groups, or modifications should be investigated for legitimacy. References:
R73, 4.1.3.8, 1, 11, 12, 13, 14, 15, 16, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-001403, CCI-001404, CCI-001405, CCI-000172, CCI-000130, CCI-002130, CCI-000135, CCI-000169, CCI-002884, CCI-000018, CCI-000015, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.1, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.6.2.1, A.6.2.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.5, 10.2.1.5, 10.2.1, 10.2, SRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000503-CTR-001275, UBTU-22-654130, SV-260628r958368_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654130
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_group
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/group already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/etc/group\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654130
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_group
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key audit_rules_usergroup_modification
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654130
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_group
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules as the recipient
for the rule
set_fact:
all_files:
- /etc/audit/rules.d/audit_rules_usergroup_modification.rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654130
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_group
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654130
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_group
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/group in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /etc/group -p wa -k audit_rules_usergroup_modification
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654130
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_group
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/group already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/etc/group\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654130
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_group
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/group in /etc/audit/audit.rules
lineinfile:
line: -w /etc/group -p wa -k audit_rules_usergroup_modification
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654130
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_group
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# 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]+/etc/group" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/etc/group $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 "wa" | 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\+/etc/group$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 /etc/group -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# 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/audit_rules_usergroup_modification.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/group" /etc/audit/rules.d/*.rules)
# 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+=("$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/audit_rules_usergroup_modification.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
# If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0600 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
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]+/etc/group" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/etc/group $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 "wa" | 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\+/etc/group$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 /etc/group -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Events that Modify User/Group Information - /etc/gshadow
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d , in order to capture events that modify
account changes:
-w /etc/gshadow -p wa -k audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file, in order to capture events that modify
account changes:
-w /etc/gshadow -p wa -k audit_rules_usergroup_modification Rationale:In addition to auditing new user and group accounts, these watches
will alert the system administrator(s) to any modifications. Any unexpected
users, groups, or modifications should be investigated for legitimacy. References:
R73, 4.1.3.8, 1, 11, 12, 13, 14, 15, 16, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-001403, CCI-001404, CCI-001405, CCI-000172, CCI-000130, CCI-002130, CCI-000135, CCI-000169, CCI-002884, CCI-000018, CCI-000015, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.1, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.6.2.1, A.6.2.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.5, 10.2.1.5, 10.2.1, 10.2, SRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000503-CTR-001275, UBTU-22-654135, SV-260629r958368_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654135
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_gshadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/gshadow already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/etc/gshadow\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654135
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_gshadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key audit_rules_usergroup_modification
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654135
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_gshadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules as the recipient
for the rule
set_fact:
all_files:
- /etc/audit/rules.d/audit_rules_usergroup_modification.rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654135
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_gshadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654135
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_gshadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/gshadow in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /etc/gshadow -p wa -k audit_rules_usergroup_modification
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654135
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_gshadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/gshadow already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/etc/gshadow\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654135
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_gshadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/gshadow in /etc/audit/audit.rules
lineinfile:
line: -w /etc/gshadow -p wa -k audit_rules_usergroup_modification
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654135
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_gshadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# 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]+/etc/gshadow" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/etc/gshadow $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 "wa" | 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\+/etc/gshadow$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 /etc/gshadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# 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/audit_rules_usergroup_modification.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/gshadow" /etc/audit/rules.d/*.rules)
# 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+=("$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/audit_rules_usergroup_modification.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
# If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0600 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
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]+/etc/gshadow" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/etc/gshadow $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 "wa" | 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\+/etc/gshadow$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 /etc/gshadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Events that Modify User/Group Information - /etc/security/opasswd
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d , in order to capture events that modify
account changes:
-w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file, in order to capture events that modify
account changes:
-w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification Rationale:In addition to auditing new user and group accounts, these watches
will alert the system administrator(s) to any modifications. Any unexpected
users, groups, or modifications should be investigated for legitimacy. References:
R73, 4.1.3.8, 1, 11, 12, 13, 14, 15, 16, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-001403, CCI-001404, CCI-001405, CCI-000172, CCI-000130, CCI-002130, CCI-000135, CCI-000169, CCI-002884, CCI-000018, CCI-000015, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.1, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.6.2.1, A.6.2.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.5, 10.2.1.5, 10.2.1, 10.2, SRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000503-CTR-001275, UBTU-22-654140, SV-260630r958368_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654140
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_opasswd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/security/opasswd already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/etc/security/opasswd\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654140
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_opasswd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key audit_rules_usergroup_modification
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654140
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_opasswd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules as the recipient
for the rule
set_fact:
all_files:
- /etc/audit/rules.d/audit_rules_usergroup_modification.rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654140
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_opasswd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654140
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_opasswd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/security/opasswd in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654140
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_opasswd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/security/opasswd already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/etc/security/opasswd\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654140
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_opasswd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/security/opasswd in /etc/audit/audit.rules
lineinfile:
line: -w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654140
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_opasswd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# 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]+/etc/security/opasswd" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/etc/security/opasswd $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 "wa" | 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\+/etc/security/opasswd$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 /etc/security/opasswd -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# 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/audit_rules_usergroup_modification.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/security/opasswd" /etc/audit/rules.d/*.rules)
# 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+=("$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/audit_rules_usergroup_modification.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
# If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0600 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
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]+/etc/security/opasswd" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/etc/security/opasswd $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 "wa" | 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\+/etc/security/opasswd$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 /etc/security/opasswd -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Events that Modify User/Group Information - /etc/passwd
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d , in order to capture events that modify
account changes:
-w /etc/passwd -p wa -k audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file, in order to capture events that modify
account changes:
-w /etc/passwd -p wa -k audit_rules_usergroup_modification Rationale:In addition to auditing new user and group accounts, these watches
will alert the system administrator(s) to any modifications. Any unexpected
users, groups, or modifications should be investigated for legitimacy. References:
R73, 4.1.3.8, 1, 11, 12, 13, 14, 15, 16, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-001403, CCI-001404, CCI-001405, CCI-000172, CCI-000130, CCI-002130, CCI-000135, CCI-000169, CCI-002884, CCI-000018, CCI-000015, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.1, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.6.2.1, A.6.2.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.5, 10.2.1.5, 10.2.1, 10.2, SRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000304-GPOS-00121, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221, SRG-OS-000274-GPOS-00104, SRG-OS-000275-GPOS-00105, SRG-OS-000276-GPOS-00106, SRG-OS-000277-GPOS-00107, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000503-CTR-001275, UBTU-22-654145, SV-260631r958368_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654145
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_passwd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/passwd already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/etc/passwd\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654145
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_passwd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key audit_rules_usergroup_modification
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654145
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_passwd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules as the recipient
for the rule
set_fact:
all_files:
- /etc/audit/rules.d/audit_rules_usergroup_modification.rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654145
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_passwd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654145
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_passwd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/passwd in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /etc/passwd -p wa -k audit_rules_usergroup_modification
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654145
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_passwd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/passwd already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/etc/passwd\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654145
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_passwd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/passwd in /etc/audit/audit.rules
lineinfile:
line: -w /etc/passwd -p wa -k audit_rules_usergroup_modification
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654145
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_passwd
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# 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]+/etc/passwd" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/etc/passwd $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 "wa" | 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\+/etc/passwd$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 /etc/passwd -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# 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/audit_rules_usergroup_modification.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/passwd" /etc/audit/rules.d/*.rules)
# 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+=("$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/audit_rules_usergroup_modification.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
# If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0600 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
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]+/etc/passwd" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/etc/passwd $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 "wa" | 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\+/etc/passwd$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 /etc/passwd -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Events that Modify User/Group Information - /etc/shadow
[ref]ruleIf the auditd daemon is configured to use the
augenrules program to read audit rules during daemon startup (the
default), add the following lines to a file with suffix .rules in the
directory /etc/audit/rules.d , in order to capture events that modify
account changes:
-w /etc/shadow -p wa -k audit_rules_usergroup_modification
If the auditd daemon is configured to use the auditctl
utility to read audit rules during daemon startup, add the following lines to
/etc/audit/audit.rules file, in order to capture events that modify
account changes:
-w /etc/shadow -p wa -k audit_rules_usergroup_modification Rationale:In addition to auditing new user and group accounts, these watches
will alert the system administrator(s) to any modifications. Any unexpected
users, groups, or modifications should be investigated for legitimacy. References:
R73, 4.1.3.8, 1, 11, 12, 13, 14, 15, 16, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-001403, CCI-001404, CCI-001405, CCI-000172, CCI-000130, CCI-002130, CCI-000135, CCI-000169, CCI-002884, CCI-000018, CCI-000015, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.1, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.6.2.1, A.6.2.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5, CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.5, 10.2.1.5, 10.2.1, 10.2, SRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255, SRG-APP-000503-CTR-001275, UBTU-22-654150, SV-260632r958368_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654150
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_shadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/shadow already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/etc/shadow\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654150
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_shadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key audit_rules_usergroup_modification
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)audit_rules_usergroup_modification$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654150
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_shadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use /etc/audit/rules.d/audit_rules_usergroup_modification.rules as the recipient
for the rule
set_fact:
all_files:
- /etc/audit/rules.d/audit_rules_usergroup_modification.rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654150
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_shadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654150
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_shadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/shadow in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /etc/shadow -p wa -k audit_rules_usergroup_modification
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654150
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_shadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /etc/shadow already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/etc/shadow\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654150
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_shadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /etc/shadow in /etc/audit/audit.rules
lineinfile:
line: -w /etc/shadow -p wa -k audit_rules_usergroup_modification
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-654150
- NIST-800-171-3.1.7
- NIST-800-53-AC-2(4)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-2(d)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.2.5
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.5
- audit_rules_usergroup_modification_shadow
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# 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]+/etc/shadow" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/etc/shadow $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 "wa" | 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\+/etc/shadow$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 /etc/shadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# 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/audit_rules_usergroup_modification.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/shadow" /etc/audit/rules.d/*.rules)
# 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+=("$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/audit_rules_usergroup_modification.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
# If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0600 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
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]+/etc/shadow" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/etc/shadow $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 "wa" | 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\+/etc/shadow$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 /etc/shadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure auditd Collects records for events that affect "/var/log/journal"
[ref]ruleAuditing the systemd journal files provides logging that can be used for
forensic purposes. Verify the system generates audit records for all events
that affect "/var/log/journal" by using the following command:
$ sudo auditctl -l | grep journal
-w /var/log/journal/ -p wa -k systemd_journal
If the command does not return a line that matches the example or the line
is commented out, this is a finding.
Note: The "-k" value is arbitrary and can be different from the example
output above.Rationale:Once an attacker establishes access to a system, the attacker often attempts
to create a persistent method of reestablishing access. One way to accomplish
this is for the attacker to modify system level binaries and their operation.
Auditing the systemd journal files provides logging that can be used for
forensic purposes. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654190
- audit_rules_var_log_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Check if watch rule for /var/log/journal/ already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/var/log/journal/\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654190
- audit_rules_var_log_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key audit_rules_var_log_journal
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)audit_rules_var_log_journal$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-22-654190
- audit_rules_var_log_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Use /etc/audit/rules.d/audit_rules_var_log_journal.rules as the recipient
for the rule
set_fact:
all_files:
- /etc/audit/rules.d/audit_rules_var_log_journal.rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-22-654190
- audit_rules_var_log_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-22-654190
- audit_rules_var_log_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Add watch rule for /var/log/journal/ in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /var/log/journal/ -p wa -k audit_rules_var_log_journal
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-22-654190
- audit_rules_var_log_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Check if watch rule for /var/log/journal/ already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/var/log/journal/\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654190
- audit_rules_var_log_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: Add watch rule for /var/log/journal/ in /etc/audit/audit.rules
lineinfile:
line: -w /var/log/journal/ -p wa -k audit_rules_var_log_journal
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- DISA-STIG-UBTU-22-654190
- audit_rules_var_log_journal
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# 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]+/var/log/journal/" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/var/log/journal/ $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 "wa" | 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\+/var/log/journal/$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 /var/log/journal/ -p wa -k audit_rules_var_log_journal" >> "$audit_rules_file"
fi
done
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# 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/audit_rules_var_log_journal.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/journal/" /etc/audit/rules.d/*.rules)
# 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+=("$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/audit_rules_var_log_journal.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/audit_rules_var_log_journal.rules"
# If the audit_rules_var_log_journal.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0600 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
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]+/var/log/journal/" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/var/log/journal/ $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 "wa" | 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\+/var/log/journal/$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 /var/log/journal/ -p wa -k audit_rules_var_log_journal" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Record Attempts to perform maintenance activities
[ref]ruleThe Ubuntu 22.04 operating system must generate audit records for
privileged activities, nonlocal maintenance, diagnostic sessions and
other system-level access.
Verify the operating system audits activities performed during nonlocal
maintenance and diagnostic sessions. Run the following command:
$ sudo auditctl -l | grep sudo.log
-w /var/log/sudo.log -p wa -k maintenance Rationale:If events associated with nonlocal administrative access or diagnostic
sessions are not logged, a major tool for assessing and investigating
attacks would not be available.
This requirement addresses auditing-related issues associated with
maintenance tools used specifically for diagnostic and repair actions
on organizational information systems.
Nonlocal maintenance and diagnostic activities are those activities
conducted by individuals communicating through a network, either an
external network (e.g., the internet) or an internal network. Local
maintenance and diagnostic activities are those activities carried
out by individuals physically present at the information system or
information system component and not communicating across a network
connection.
This requirement applies to hardware/software diagnostic test
equipment or tools. This requirement does not cover hardware/software
components that may support information system maintenance, yet are a
part of the system, for example, the software implementing "ping,"
"ls," "ipconfig," or the hardware and software implementing the
monitoring port of an Ethernet switch. References:
R73, 4.1.3.3, CCI-000172, CCI-002884, Req-10.2.2, Req-10.2.5.b, 10.2.1.3, 10.2.1, 10.2, SRG-OS-000392-GPOS-00172, SRG-OS-000471-GPOS-00215, UBTU-22-654235, SV-260649r986298_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | true |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-654235
- PCI-DSS-Req-10.2.2
- PCI-DSS-Req-10.2.5.b
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.3
- audit_sudo_log_events
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /var/log/sudo.log already exists in /etc/audit/rules.d/
find:
paths: /etc/audit/rules.d
contains: ^\s*-w\s+/var/log/sudo.log\s+-p\s+wa(\s|$)+
patterns: '*.rules'
register: find_existing_watch_rules_d
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654235
- PCI-DSS-Req-10.2.2
- PCI-DSS-Req-10.2.5.b
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.3
- audit_sudo_log_events
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Search /etc/audit/rules.d for other rules with specified key logins
find:
paths: /etc/audit/rules.d
contains: ^.*(?:-F key=|-k\s+)logins$
patterns: '*.rules'
register: find_watch_key
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-22-654235
- PCI-DSS-Req-10.2.2
- PCI-DSS-Req-10.2.5.b
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.3
- audit_sudo_log_events
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use /etc/audit/rules.d/logins.rules as the recipient for the rule
set_fact:
all_files:
- /etc/audit/rules.d/logins.rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-22-654235
- PCI-DSS-Req-10.2.2
- PCI-DSS-Req-10.2.5.b
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.3
- audit_sudo_log_events
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Use matched file as the recipient for the rule
set_fact:
all_files:
- '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
is defined and find_existing_watch_rules_d.matched == 0
tags:
- DISA-STIG-UBTU-22-654235
- PCI-DSS-Req-10.2.2
- PCI-DSS-Req-10.2.5.b
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.3
- audit_sudo_log_events
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /var/log/sudo.log in /etc/audit/rules.d/
lineinfile:
path: '{{ all_files[0] }}'
line: -w /var/log/sudo.log -p wa -k logins
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
== 0
tags:
- DISA-STIG-UBTU-22-654235
- PCI-DSS-Req-10.2.2
- PCI-DSS-Req-10.2.5.b
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.3
- audit_sudo_log_events
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Check if watch rule for /var/log/sudo.log already exists in /etc/audit/audit.rules
find:
paths: /etc/audit/
contains: ^\s*-w\s+/var/log/sudo.log\s+-p\s+wa(\s|$)+
patterns: audit.rules
register: find_existing_watch_audit_rules
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-654235
- PCI-DSS-Req-10.2.2
- PCI-DSS-Req-10.2.5.b
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.3
- audit_sudo_log_events
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
- name: Add watch rule for /var/log/sudo.log in /etc/audit/audit.rules
lineinfile:
line: -w /var/log/sudo.log -p wa -k logins
state: present
dest: /etc/audit/audit.rules
create: true
mode: '0600'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
- find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
== 0
tags:
- DISA-STIG-UBTU-22-654235
- PCI-DSS-Req-10.2.2
- PCI-DSS-Req-10.2.5.b
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- PCI-DSSv4-10.2.1.3
- audit_sudo_log_events
- low_complexity
- low_disruption
- medium_severity
- reboot_required
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
# into the list of files to be inspected
files_to_inspect+=('/etc/audit/audit.rules')
# 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]+/var/log/sudo.log" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/var/log/sudo.log $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 "wa" | 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\+/var/log/sudo.log$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 /var/log/sudo.log -p wa -k logins" >> "$audit_rules_file"
fi
done
# 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 |
# -----------------------------------------------------------------------------------------
files_to_inspect=()
# 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/logins.rules' to list of files for inspection.
readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/sudo.log" /etc/audit/rules.d/*.rules)
# 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+=("$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/logins.rules' into list of files for inspection
key_rule_file="/etc/audit/rules.d/logins.rules"
# If the logins.rules file doesn't exist yet, create it with correct permissions
if [ ! -e "$key_rule_file" ]
then
touch "$key_rule_file"
chmod 0600 "$key_rule_file"
fi
files_to_inspect+=("$key_rule_file")
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]+/var/log/sudo.log" "$audit_rules_file"
then
# Rule is found => verify yet if existing rule definition contains
# all of the required access type bits
# Define BRE whitespace class shortcut
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\+/var/log/sudo.log $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 "wa" | 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\+/var/log/sudo.log$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 /var/log/sudo.log -p wa -k logins" >> "$audit_rules_file"
fi
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
System Audit Logs Must Have Mode 0750 or Less Permissive
[ref]rule
If log_group in /etc/audit/auditd.conf is set to a group other than the root
group account, change the mode of the audit log files with the following command:
$ sudo chmod 0750 /var/log/audit
Otherwise, change the mode of the audit log files with the following command:
$ sudo chmod 0700 /var/log/audit Rationale:If users can write to audit logs, audit trails can be modified or destroyed. References:
4.1.4.4, 1, 11, 12, 13, 14, 15, 16, 18, 19, 3, 4, 5, 6, 7, 8, APO01.06, APO11.04, APO12.06, BAI03.05, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, DSS06.02, MEA02.01, CCI-000162, CCI-000163, CCI-000164, 4.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.7.3, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 5.2, SR 6.1, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R5.1.1, CIP-003-8 R5.2, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-004-6 R3.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2, CIP-007-3 R6.5, CM-6(a), AC-6(1), AU-9, DE.AE-3, DE.AE-5, PR.AC-4, PR.DS-5, PR.PT-1, RS.AN-1, RS.AN-4, SRG-OS-000057-GPOS-00027, SRG-OS-000058-GPOS-00028, SRG-OS-000059-GPOS-00029, UBTU-22-653060, SV-260600r958438_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
if LC_ALL=C grep -iw ^log_file /etc/audit/auditd.conf; then
DIR=$(awk -F "=" '/^log_file/ {print $2}' /etc/audit/auditd.conf | tr -d ' ' | rev | cut -d"/" -f2- | rev)
else
DIR="/var/log/audit"
fi
if LC_ALL=C grep -m 1 -q ^log_group /etc/audit/auditd.conf; then
GROUP=$(awk -F "=" '/log_group/ {print $2}' /etc/audit/auditd.conf | tr -d ' ')
if ! [ "${GROUP}" == 'root' ] ; then
chmod 0750 $DIR
else
chmod 0700 $DIR
fi
else
chmod 0700 $DIR
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
System Audit Logs Must Be Group Owned By Root
[ref]ruleAll audit logs must be group owned by root user.
Determine where the audit logs are stored with the following command:
$ sudo grep -iw log_file /etc/audit/auditd.conf
log_file = /var/log/audit/audit.log
Using the path of the directory containing the audit logs, determine if the audit log files
are owned by the "root" group by using the following command:
$ sudo stat -c "%n %G" /var/log/audit/*
/var/log/audit/audit.log root
If the audit log files are owned by a group other than "root", this is a finding.
To remediate, configure the audit log directory and its underlying files to be owned by "root"
group.
Set the "log_group" parameter of the audit configuration file to the "root" value so when a
new log file is created, its group owner is properly set:
$ sudo sed -i '/^log_group/D' /etc/audit/auditd.conf
$ sudo sed -i /^log_file/a'log_group = root' /etc/audit/auditd.conf
Last, signal the audit daemon to reload the configuration file to update the group owners
of existing files:
$ sudo systemctl kill auditd -s SIGHUP Rationale:Unauthorized disclosure of audit records can reveal system and configuration data to
attackers, thus compromising its confidentiality. Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
if LC_ALL=C grep -iw log_file /etc/audit/auditd.conf; then
FILE=$(awk -F "=" '/^log_file/ {print $2}' /etc/audit/auditd.conf | tr -d ' ')
else
FILE="/var/log/audit/audit.log"
fi
if [ -e "/etc/audit/auditd.conf" ] ; then
LC_ALL=C sed -i "/^\s*log_group\s*=\s*/Id" "/etc/audit/auditd.conf"
else
touch "/etc/audit/auditd.conf"
fi
# make sure file has newline at the end
sed -i -e '$a\' "/etc/audit/auditd.conf"
cp "/etc/audit/auditd.conf" "/etc/audit/auditd.conf.bak"
# Insert at the end of the file
printf '%s\n' "log_group = root" >> "/etc/audit/auditd.conf"
# Clean up after ourselves.
rm "/etc/audit/auditd.conf.bak"
chgrp root $FILE
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Audit Configuration Files Must Be Owned By Group root
[ref]ruleAll audit configuration files must be owned by group root.
chown :root /etc/audit/audit*.{rules,conf} /etc/audit/rules.d/* Rationale:Without the capability to restrict which roles and individuals can
select which events are audited, unauthorized personnel may be able
to prevent the auditing of critical events.
Misconfigured audits may degrade the system's performance by
overwhelming the audit log. Misconfigured audits may also make it more
difficult to establish, correlate, and investigate the events relating
to an incident or identify those responsible for one. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-653075
- configure_strategy
- file_groupownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /etc/audit/ file(s) matching ^.*audit(\.rules|d\.conf)$
command: find -H /etc/audit/ -maxdepth 1 -type f ! -group 0 -regextype posix-extended
-regex "^.*audit(\.rules|d\.conf)$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653075
- configure_strategy
- file_groupownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /etc/audit/ file(s) matching ^.*audit(\.rules|d\.conf)$
file:
path: '{{ item }}'
group: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653075
- configure_strategy
- file_groupownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /etc/audit/rules.d/ file(s) matching ^.*\.rules$
command: find -H /etc/audit/rules.d/ -maxdepth 1 -type f ! -group 0 -regextype posix-extended
-regex "^.*\.rules$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653075
- configure_strategy
- file_groupownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure group owner on /etc/audit/rules.d/ file(s) matching ^.*\.rules$
file:
path: '{{ item }}'
group: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653075
- configure_strategy
- file_groupownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
find -L /etc/audit/ -maxdepth 1 -type f ! -group 0 -regextype posix-extended -regex '^.*audit(\.rules|d\.conf)$' -exec chgrp -L 0 {} \;
find -L /etc/audit/rules.d/ -maxdepth 1 -type f ! -group 0 -regextype posix-extended -regex '^.*\.rules$' -exec chgrp -L 0 {} \;
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Audit Configuration Files Must Be Owned By Root
[ref]ruleAll audit configuration files must be owned by root user.
To properly set the owner of /etc/audit/ , run the command:
$ sudo chown root /etc/audit/
To properly set the owner of /etc/audit/rules.d/ , run the command:
$ sudo chown root /etc/audit/rules.d/ Rationale:Without the capability to restrict which roles and individuals can
select which events are audited, unauthorized personnel may be able
to prevent the auditing of critical events.
Misconfigured audits may degrade the system's performance by
overwhelming the audit log. Misconfigured audits may also make it more
difficult to establish, correlate, and investigate the events relating
to an incident or identify those responsible for one. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-653070
- configure_strategy
- file_ownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /etc/audit/ file(s) matching ^.*audit(\.rules|d\.conf)$
command: find -H /etc/audit/ -maxdepth 1 -type f ! -uid 0 -regextype posix-extended
-regex "^.*audit(\.rules|d\.conf)$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653070
- configure_strategy
- file_ownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on /etc/audit/ file(s) matching ^.*audit(\.rules|d\.conf)$
file:
path: '{{ item }}'
owner: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653070
- configure_strategy
- file_ownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /etc/audit/rules.d/ file(s) matching ^.*\.rules$
command: find -H /etc/audit/rules.d/ -maxdepth 1 -type f ! -uid 0 -regextype posix-extended
-regex "^.*\.rules$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653070
- configure_strategy
- file_ownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner on /etc/audit/rules.d/ file(s) matching ^.*\.rules$
file:
path: '{{ item }}'
owner: '0'
state: file
with_items:
- '{{ files_found.stdout_lines }}'
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653070
- configure_strategy
- file_ownership_audit_configuration
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
find -L /etc/audit/ -maxdepth 1 -type f ! -uid 0 -regextype posix-extended -regex '^.*audit(\.rules|d\.conf)$' -exec chown -L 0 {} \;
find -L /etc/audit/rules.d/ -maxdepth 1 -type f ! -uid 0 -regextype posix-extended -regex '^.*\.rules$' -exec chown -L 0 {} \;
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
System Audit Logs Must Be Owned By Root
[ref]ruleAll audit logs must be owned by root user. The path for audit log can be
configured via log_file parameter in /etc/audit/auditd.conf
or by default, the path for audit log is /var/log/audit/ .
To properly set the owner of /var/log/audit/* , run the command:
$ sudo chown root /var/log/audit/* Rationale:Unauthorized disclosure of audit records can reveal system and configuration data to
attackers, thus compromising its confidentiality. References:
4.1.4.2, 1, 11, 12, 13, 14, 15, 16, 18, 19, 3, 4, 5, 6, 7, 8, 5.4.1.1, APO01.06, APO11.04, APO12.06, BAI03.05, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, DSS06.02, MEA02.01, 3.3.1, CCI-000162, CCI-000163, CCI-000164, CCI-001314, 4.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.7.3, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 5.2, SR 6.1, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CM-6(a), AC-6(1), AU-9(4), DE.AE-3, DE.AE-5, PR.AC-4, PR.DS-5, PR.PT-1, RS.AN-1, RS.AN-4, Req-10.5.1, SRG-OS-000057-GPOS-00027, SRG-OS-000058-GPOS-00028, SRG-OS-000059-GPOS-00029, SRG-OS-000206-GPOS-00084, UBTU-22-653050, SV-260598r958434_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
if LC_ALL=C grep -iw log_file /etc/audit/auditd.conf; then
FILE=$(awk -F "=" '/^log_file/ {print $2}' /etc/audit/auditd.conf | tr -d ' ')
chown root $FILE*
else
chown root /var/log/audit/audit.log*
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
System Audit Logs Must Have Mode 0600 or Less Permissive
[ref]ruleDetermine where the audit logs are stored with the following command:
$ sudo grep -iw log_file /etc/audit/auditd.conf
log_file = /var/log/audit/audit.log
Using the path of the directory containing the audit logs, determine
if the audit log files have a mode of "600" or less by using the following command:
$ sudo stat -c "%n %a" /var/log/audit/* Rationale:If users can write to audit logs, audit trails can be modified or destroyed. Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
if LC_ALL=C grep -iqw ^log_file /etc/audit/auditd.conf; then
FILE=$(awk -F "=" '/^log_file/ {print $2}' /etc/audit/auditd.conf | tr -d ' ')
else
FILE="/var/log/audit/audit.log"
fi
chmod 0600 -- "$(dirname "$FILE")"/*
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure auditd Data Retention
[ref]groupThe audit system writes data to /var/log/audit/audit.log . By default,
auditd rotates 5 logs by size (6MB), retaining a maximum of 30MB of
data in total, and refuses to write entries when the disk is too
full. This minimizes the risk of audit data filling its partition
and impacting other services. This also minimizes the risk of the audit
daemon temporarily disabling the system if it cannot write audit log (which
it can be configured to do).
For a busy
system or a system which is thoroughly auditing system activity, the default settings
for data retention may be
insufficient. The log file size needed will depend heavily on what types
of events are being audited. First configure auditing to log all the events of
interest. Then monitor the log size manually for awhile to determine what file
size will allow you to keep the required data for the correct time period.
Using a dedicated partition for /var/log/audit prevents the
auditd logs from disrupting system functionality if they fill, and,
more importantly, prevents other activity in /var from filling the
partition and stopping the audit trail. (The audit logs are size-limited and
therefore unlikely to grow without bound unless configured to do so.) Some
machines may have requirements that no actions occur which cannot be audited.
If this is the case, then auditd can be configured to halt the machine
if it runs out of space. Note: Since older logs are rotated,
configuring auditd this way does not prevent older logs from being
rotated away before they can be viewed.
If your system is configured to halt when logging cannot be performed, make
sure this can never happen under normal circumstances! Ensure that
/var/log/audit is on its own partition, and that this partition is
larger than the maximum amount of data auditd will retain
normally. |
contains 7 rules |
Configure audispd Plugin To Send Logs To Remote Server
[ref]ruleConfigure the audispd plugin to off-load audit records onto a different
system or media from the system being audited.
First, set the active option in
/etc/audisp/plugins.d/au-remote.conf
Set the remote_server option in /etc/audit/audisp-remote.conf
with an IP address or hostname of the system that the audispd plugin should
send audit records to. For example
remote_server = logcollector Rationale:Information stored in one location is vulnerable to accidental or incidental
deletion or alteration.Off-loading is a common process in information systems
with limited audit storage capacity. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-653020
- auditd_audispd_configure_remote_server
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: XCCDF Value var_audispd_remote_server # promote to variable
set_fact:
var_audispd_remote_server: !!str logcollector
tags:
- always
- name: Configure audispd Plugin To Send Logs To Remote Server - Uncomment active
for offloading to remote server
ansible.builtin.lineinfile:
path: /etc/audit/plugins.d/au-remote.conf
regexp: ^(#.*)(active\s*=)
line: \2
backrefs: true
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653020
- auditd_audispd_configure_remote_server
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Configure audispd Plugin To Send Logs To Remote Server - Set active to true
for offloading to remote server
ansible.builtin.lineinfile:
path: /etc/audit/plugins.d/au-remote.conf
regexp: ^(.*)(active\s*=)(?!.*yes)
line: \2 yes
create: true
state: present
backrefs: true
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653020
- auditd_audispd_configure_remote_server
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Configure audispd Plugin To Send Logs To Remote Server - Make sure that a
remote server is configured for Audispd
ansible.builtin.lineinfile:
path: /etc/audit/audisp-remote.conf
line: remote_server = {{ var_audispd_remote_server }}
regexp: ^\s*remote_server\s*=.*$
create: true
state: present
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653020
- auditd_audispd_configure_remote_server
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
var_audispd_remote_server='logcollector'
AUDITCONFIG=/etc/audit/audisp-remote.conf
AUREMOTECONFIG=/etc/audit/plugins.d/au-remote.conf
if [ -e "$AUREMOTECONFIG" ] ; then
LC_ALL=C sed -i "/^\s*active\s*=\s*/Id" "$AUREMOTECONFIG"
else
printf '%s\n' "Path '$AUREMOTECONFIG' wasn't found on this system. Refusing to continue." >&2
return 1
fi
# make sure file has newline at the end
sed -i -e '$a\' "$AUREMOTECONFIG"
cp "$AUREMOTECONFIG" "$AUREMOTECONFIG.bak"
# Insert at the end of the file
printf '%s\n' "active = yes" >> "$AUREMOTECONFIG"
# Clean up after ourselves.
rm "$AUREMOTECONFIG.bak"
if [ -e "$AUDITCONFIG" ] ; then
LC_ALL=C sed -i "/^\s*remote_server\s*=\s*/Id" "$AUDITCONFIG"
else
printf '%s\n' "Path '$AUDITCONFIG' wasn't found on this system. Refusing to continue." >&2
return 1
fi
# make sure file has newline at the end
sed -i -e '$a\' "$AUDITCONFIG"
cp "$AUDITCONFIG" "$AUDITCONFIG.bak"
# Insert at the end of the file
printf '%s\n' "remote_server = $var_audispd_remote_server" >> "$AUDITCONFIG"
# Clean up after ourselves.
rm "$AUDITCONFIG.bak"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure a Sufficiently Large Partition for Audit Logs
[ref]ruleThe Ubuntu 22.04 operating system must allocate audit record storage
capacity to store at least one weeks worth of audit records when audit
records are not immediately sent to a central audit record storage
facility.
The partition size needed to capture a week's worth of audit records is
based on the activity level of the system and the total storage capacity
available.
Determine which partition the audit records are being written to with the
following command:
$ sudo grep log_file /etc/audit/auditd.conf
log_file = /var/log/audit/audit.log
Check the size of the partition that audit records are written to with the
following command:
$ sudo df -h /var/log/audit/
/dev/sda2 24G 10.4G 13.6G 43% /var/log/audit Rationale:Information stored in one location is vulnerable to accidental or incidental
deletion or alteration. Off-loading is a common process in information
systems with limited audit storage capacity. |
Configure auditd Disk Full Action when Disk Space Is Full
[ref]ruleThe auditd service can be configured to take an action
when disk space is running low but prior to running out of space completely.
Edit the file /etc/audit/auditd.conf . Add or modify the following line,
substituting ACTION appropriately:
disk_full_action = ACTION
Set this value to single to cause the system to switch to single-user
mode for corrective action. Acceptable values also include
syslog , exec , single , and halt
For certain systems, the need for availability
outweighs the need to log all actions, and a different setting should be
determined. Details regarding all possible values for ACTION are described in the
auditd.conf man page.Rationale:Taking appropriate action in case of a filled audit storage volume will minimize
the possibility of losing audit records. References:
1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, APO11.04, APO12.06, APO13.01, BAI03.05, BAI04.04, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, MEA02.01, CCI-000140, 4.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 7.1, SR 7.2, A.12.1.3, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.16.1.4, A.16.1.5, A.16.1.7, A.17.2.1, AU-5(b), AU-5(2), AU-5(1), AU-5(4), CM-6(a), DE.AE-3, DE.AE-5, PR.DS-4, PR.PT-1, RS.AN-1, RS.AN-4, SRG-OS-000047-GPOS-00023, UBTU-22-653030, SV-260594r958426_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
var_auditd_disk_full_action='halt'
var_auditd_disk_full_action="$(echo $var_auditd_disk_full_action | cut -d \| -f 1)"
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^disk_full_action")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_disk_full_action"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^disk_full_action\\>" "/etc/audit/auditd.conf"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^disk_full_action\\>.*/$escaped_formatted_output/gi" "/etc/audit/auditd.conf"
else
if [[ -s "/etc/audit/auditd.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/audit/auditd.conf" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/audit/auditd.conf"
fi
printf '%s\n' "$formatted_output" >> "/etc/audit/auditd.conf"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure auditd mail_acct Action on Low Disk Space
[ref]ruleThe auditd service can be configured to send email to
a designated account in certain situations. Add or correct the following line
in /etc/audit/auditd.conf to ensure that administrators are notified
via email for those situations:
action_mail_acct = root Rationale:Email sent to the root account is typically aliased to the
administrators of the system, who can take appropriate action. References:
4.1.2.3, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 5.4.1.1, APO11.04, APO12.06, APO13.01, BAI03.05, BAI04.04, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, MEA02.01, 3.3.1, CCI-001855, CCI-000139, 164.312(a)(2)(ii), 4.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 7.1, SR 7.2, A.12.1.3, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.16.1.4, A.16.1.5, A.16.1.7, A.17.2.1, CIP-003-8 R1.3, CIP-003-8 R3, CIP-003-8 R3.1, CIP-003-8 R3.2, CIP-003-8 R3.3, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3, IA-5(1), AU-5(a), AU-5(2), CM-6(a), DE.AE-3, DE.AE-5, PR.DS-4, PR.PT-1, RS.AN-1, RS.AN-4, Req-10.7.a, SRG-OS-000046-GPOS-00022, SRG-OS-000343-GPOS-00134, UBTU-22-653025, SV-260593r958424_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
var_auditd_action_mail_acct='root'
AUDITCONFIG=/etc/audit/auditd.conf
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^action_mail_acct")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_action_mail_acct"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^action_mail_acct\\>" "$AUDITCONFIG"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^action_mail_acct\\>.*/$escaped_formatted_output/gi" "$AUDITCONFIG"
else
if [[ -s "$AUDITCONFIG" ]] && [[ -n "$(tail -c 1 -- "$AUDITCONFIG" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "$AUDITCONFIG"
fi
printf '%s\n' "$formatted_output" >> "$AUDITCONFIG"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure auditd space_left Action on Low Disk Space
[ref]ruleThe auditd service can be configured to take an action
when disk space starts to run low.
Edit the file /etc/audit/auditd.conf . Modify the following line,
substituting ACTION appropriately:
space_left_action = ACTION
Possible values for ACTION are described in the auditd.conf man page.
These include:
syslog email exec suspend single halt
Set this to email (instead of the default,
which is suspend ) as it is more likely to get prompt attention. Acceptable values
also include suspend , single , and halt .Rationale:Notifying administrators of an impending disk space problem may
allow them to take corrective action prior to any disruption. References:
4.1.2.3, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 5.4.1.1, APO11.04, APO12.06, APO13.01, BAI03.05, BAI04.04, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, MEA02.01, 3.3.1, CCI-001855, 164.312(a)(2)(ii), 4.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 7.1, SR 7.2, A.12.1.3, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.16.1.4, A.16.1.5, A.16.1.7, A.17.2.1, AU-5(b), AU-5(2), AU-5(1), AU-5(4), CM-6(a), DE.AE-3, DE.AE-5, PR.DS-4, PR.PT-1, RS.AN-1, RS.AN-4, Req-10.7, 10.5.1, 10.5, SRG-OS-000343-GPOS-00134, UBTU-22-653040, SV-260596r971542_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
var_auditd_space_left_action='email'
var_auditd_space_left_action="$(echo $var_auditd_space_left_action | cut -d \| -f 1)"
#
# If space_left_action present in /etc/audit/auditd.conf, change value
# to var_auditd_space_left_action, else
# add "space_left_action = $var_auditd_space_left_action" to /etc/audit/auditd.conf
#
AUDITCONFIG=/etc/audit/auditd.conf
# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^space_left_action")
# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_auditd_space_left_action"
# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^space_left_action\\>" "$AUDITCONFIG"; then
escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
LC_ALL=C sed -i --follow-symlinks "s/^space_left_action\\>.*/$escaped_formatted_output/gi" "$AUDITCONFIG"
else
if [[ -s "$AUDITCONFIG" ]] && [[ -n "$(tail -c 1 -- "$AUDITCONFIG" || true)" ]]; then
LC_ALL=C sed -i --follow-symlinks '$a'\\ "$AUDITCONFIG"
fi
printf '%s\n' "$formatted_output" >> "$AUDITCONFIG"
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Configure auditd space_left on Low Disk Space
[ref]ruleThe auditd service can be configured to take an action
when disk space is running low but prior to running out of space completely.
Edit the file /etc/audit/auditd.conf . Add or modify the following line,
substituting PERCENTAGE appropriately:
space_left = PERCENTAGE%
Set this value to at least 25 to cause the system to
notify the user of an issue.Rationale:Notifying administrators of an impending disk space problem may allow them to
take corrective action prior to any disruption. References:
1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, APO11.04, APO12.06, APO13.01, BAI03.05, BAI04.04, BAI08.02, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.04, DSS05.07, MEA02.01, CCI-001855, 4.2.3.10, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 7.1, SR 7.2, A.12.1.3, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.16.1.4, A.16.1.5, A.16.1.7, A.17.2.1, AU-5(b), AU-5(2), AU-5(1), AU-5(4), CM-6(a), DE.AE-3, DE.AE-5, PR.DS-4, PR.PT-1, RS.AN-1, RS.AN-4, Req-10.7, SRG-OS-000343-GPOS-00134, UBTU-22-653040, SV-260596r971542_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | restrict |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-653040
- NIST-800-53-AU-5(1)
- NIST-800-53-AU-5(2)
- NIST-800-53-AU-5(4)
- NIST-800-53-AU-5(b)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.7
- auditd_data_retention_space_left_percentage
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
- name: XCCDF Value var_auditd_space_left_percentage # promote to variable
set_fact:
var_auditd_space_left_percentage: !!str 25
tags:
- always
- name: Configure auditd space_left on Low Disk Space
lineinfile:
dest: /etc/audit/auditd.conf
line: space_left = {{ var_auditd_space_left_percentage }}%
regexp: ^\s*space_left\s*=\s*.*$
state: present
create: true
when:
- '"auditd" in ansible_facts.packages'
- '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653040
- NIST-800-53-AU-5(1)
- NIST-800-53-AU-5(2)
- NIST-800-53-AU-5(4)
- NIST-800-53-AU-5(b)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.7
- auditd_data_retention_space_left_percentage
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- restrict_strategy
Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; then
var_auditd_space_left_percentage='25'
grep -q "^space_left[[:space:]]*=.*$" /etc/audit/auditd.conf && \
sed -i "s/^space_left[[:space:]]*=.*$/space_left = $var_auditd_space_left_percentage%/g" /etc/audit/auditd.conf || \
echo "space_left = $var_auditd_space_left_percentage%" >> /etc/audit/auditd.conf
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Offload audit Logs to External Media
[ref]ruleThe operating system must have a crontab script running weekly to
offload audit events of standalone systems. Warning:
Due to different needs and possibilities, automated remediation is not
available for this configuration check. Rationale:Information stored in one location is vulnerable to accidental or
incidental deletion or alteration.
Offloading is a common process in information systems with limited
audit storage capacity. |
System Accounting with auditd
[ref]groupThe audit service provides substantial capabilities
for recording system activities. This section
deals with permissions of auditd related files. |
contains 5 rules |
Verify that audit tools are owned by root
[ref]ruleThe Ubuntu 22.04 operating system audit tools must have the proper
ownership configured to protected against unauthorized access.
Verify it by running the following command:
$ stat -c "%n %U" /sbin/auditctl /sbin/aureport /sbin/ausearch /sbin/autrace /sbin/auditd /sbin/audispd /sbin/augenrules
/sbin/auditctl root
/sbin/aureport root
/sbin/ausearch root
/sbin/autrace root
/sbin/auditd root
/sbin/audispd root
/sbin/augenrules root
Audit tools needed to successfully view and manipulate audit information
system activity and records. Audit tools include custom queries and report
generatorsRationale:Protecting audit information also includes identifying and protecting the
tools used to view and manipulate log data. Therefore, protecting audit
tools is necessary to prevent unauthorized operation on audit information.
Operating systems providing tools to interface with audit information
will leverage user permissions and roles identifying the user accessing the
tools and the corresponding rights the user enjoys to make access decisions
regarding the access to audit tools. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-232110
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/auditctl
stat:
path: /sbin/auditctl
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232110
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner 0 on /sbin/auditctl
file:
path: /sbin/auditctl
owner: '0'
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232110
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/aureport
stat:
path: /sbin/aureport
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232110
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner 0 on /sbin/aureport
file:
path: /sbin/aureport
owner: '0'
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232110
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/ausearch
stat:
path: /sbin/ausearch
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232110
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner 0 on /sbin/ausearch
file:
path: /sbin/ausearch
owner: '0'
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232110
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/autrace
stat:
path: /sbin/autrace
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232110
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner 0 on /sbin/autrace
file:
path: /sbin/autrace
owner: '0'
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232110
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/auditd
stat:
path: /sbin/auditd
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232110
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner 0 on /sbin/auditd
file:
path: /sbin/auditd
owner: '0'
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232110
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/audispd
stat:
path: /sbin/audispd
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232110
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner 0 on /sbin/audispd
file:
path: /sbin/audispd
owner: '0'
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232110
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/augenrules
stat:
path: /sbin/augenrules
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232110
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure owner 0 on /sbin/augenrules
file:
path: /sbin/augenrules
owner: '0'
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232110
- configure_strategy
- file_ownership_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
chown 0 /sbin/auditctl
chown 0 /sbin/aureport
chown 0 /sbin/ausearch
chown 0 /sbin/autrace
chown 0 /sbin/auditd
chown 0 /sbin/audispd
chown 0 /sbin/augenrules
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Verify that audit tools Have Mode 0755 or less
[ref]ruleThe Ubuntu 22.04 operating system audit tools must have the proper
permissions configured to protected against unauthorized access.
Verify it by running the following command:
$ stat -c "%n %a" /sbin/auditctl /sbin/aureport /sbin/ausearch /sbin/autrace /sbin/auditd /sbin/audispd /sbin/augenrules
/sbin/auditctl 755
/sbin/aureport 755
/sbin/ausearch 755
/sbin/autrace 755
/sbin/auditd 755
/sbin/audispd 755
/sbin/augenrules 755
Audit tools needed to successfully view and manipulate audit information
system activity and records. Audit tools include custom queries and report
generatorsRationale:Protecting audit information also includes identifying and protecting the
tools used to view and manipulate log data. Therefore, protecting audit
tools is necessary to prevent unauthorized operation on audit information.
Operating systems providing tools to interface with audit information
will leverage user permissions and roles identifying the user accessing the
tools and the corresponding rights the user enjoys to make access decisions
regarding the access to audit tools. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-232035
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/auditctl
stat:
path: /sbin/auditctl
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232035
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-s,g-ws,o-wt on /sbin/auditctl
file:
path: /sbin/auditctl
mode: u-s,g-ws,o-wt
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232035
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/aureport
stat:
path: /sbin/aureport
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232035
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-s,g-ws,o-wt on /sbin/aureport
file:
path: /sbin/aureport
mode: u-s,g-ws,o-wt
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232035
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/ausearch
stat:
path: /sbin/ausearch
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232035
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-s,g-ws,o-wt on /sbin/ausearch
file:
path: /sbin/ausearch
mode: u-s,g-ws,o-wt
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232035
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/autrace
stat:
path: /sbin/autrace
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232035
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-s,g-ws,o-wt on /sbin/autrace
file:
path: /sbin/autrace
mode: u-s,g-ws,o-wt
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232035
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/auditd
stat:
path: /sbin/auditd
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232035
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-s,g-ws,o-wt on /sbin/auditd
file:
path: /sbin/auditd
mode: u-s,g-ws,o-wt
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232035
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/audispd
stat:
path: /sbin/audispd
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232035
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-s,g-ws,o-wt on /sbin/audispd
file:
path: /sbin/audispd
mode: u-s,g-ws,o-wt
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232035
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /sbin/augenrules
stat:
path: /sbin/augenrules
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-232035
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-s,g-ws,o-wt on /sbin/augenrules
file:
path: /sbin/augenrules
mode: u-s,g-ws,o-wt
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-232035
- configure_strategy
- file_permissions_audit_binaries
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
chmod u-s,g-ws,o-wt /sbin/auditctl
chmod u-s,g-ws,o-wt /sbin/aureport
chmod u-s,g-ws,o-wt /sbin/ausearch
chmod u-s,g-ws,o-wt /sbin/autrace
chmod u-s,g-ws,o-wt /sbin/auditd
chmod u-s,g-ws,o-wt /sbin/audispd
chmod u-s,g-ws,o-wt /sbin/augenrules
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Verify Permissions on /etc/audit/auditd.conf
[ref]rule
To properly set the permissions of /etc/audit/auditd.conf , run the command:
$ sudo chmod 0640 /etc/audit/auditd.conf Rationale:Without the capability to restrict the roles and individuals that can select which events
are audited, unauthorized personnel may be able to prevent the auditing of critical
events. Misconfigured audits may degrade the system's performance by overwhelming
the audit log. Misconfigured audits may also make it more difficult to establish,
correlate, and investigate the events relating to an incident or identify
those responsible for one. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-653065
- NIST-800-53-AU-12(b)
- configure_strategy
- file_permissions_etc_audit_auditd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /etc/audit/auditd.conf
stat:
path: /etc/audit/auditd.conf
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653065
- NIST-800-53-AU-12(b)
- configure_strategy
- file_permissions_etc_audit_auditd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-xs,g-xws,o-xwrt on /etc/audit/auditd.conf
file:
path: /etc/audit/auditd.conf
mode: u-xs,g-xws,o-xwrt
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-653065
- NIST-800-53-AU-12(b)
- configure_strategy
- file_permissions_etc_audit_auditd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
chmod u-xs,g-xws,o-xwrt /etc/audit/auditd.conf
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Verify Permissions on /etc/audit/audit.rules
[ref]rule
To properly set the permissions of /etc/audit/audit.rules , run the command:
$ sudo chmod 0640 /etc/audit/audit.rules Rationale:Without the capability to restrict the roles and individuals that can select which events
are audited, unauthorized personnel may be able to prevent the auditing of critical
events. Misconfigured audits may degrade the system's performance by overwhelming
the audit log. Misconfigured audits may also make it more difficult to establish,
correlate, and investigate the events relating to an incident or identify
those responsible for one. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-653065
- configure_strategy
- file_permissions_etc_audit_rules
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Test for existence /etc/audit/audit.rules
stat:
path: /etc/audit/audit.rules
register: file_exists
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653065
- configure_strategy
- file_permissions_etc_audit_rules
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Ensure permission u-xs,g-xws,o-xwrt on /etc/audit/audit.rules
file:
path: /etc/audit/audit.rules
mode: u-xs,g-xws,o-xwrt
when:
- '"linux-base" in ansible_facts.packages'
- file_exists.stat is defined and file_exists.stat.exists
tags:
- DISA-STIG-UBTU-22-653065
- configure_strategy
- file_permissions_etc_audit_rules
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
chmod u-xs,g-xws,o-xwrt /etc/audit/audit.rules
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Verify Permissions on /etc/audit/rules.d/*.rules
[ref]rule
To properly set the permissions of /etc/audit/rules.d/*.rules , run the command:
$ sudo chmod 0600 /etc/audit/rules.d/*.rules Rationale:Without the capability to restrict the roles and individuals that can select which events
are audited, unauthorized personnel may be able to prevent the auditing of critical
events. Misconfigured audits may degrade the system's performance by overwhelming
the audit log. Misconfigured audits may also make it more difficult to establish,
correlate, and investigate the events relating to an incident or identify
those responsible for one. Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-653065
- NIST-800-53-AU-12(b)
- configure_strategy
- file_permissions_etc_audit_rulesd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Find /etc/audit/rules.d/ file(s)
command: find -H /etc/audit/rules.d/ -maxdepth 1 -perm /u+xs,g+xwrs,o+xwrt -type
f -regextype posix-extended -regex "^.*rules$"
register: files_found
changed_when: false
failed_when: false
check_mode: false
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653065
- NIST-800-53-AU-12(b)
- configure_strategy
- file_permissions_etc_audit_rulesd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Set permissions for /etc/audit/rules.d/ file(s)
file:
path: '{{ item }}'
mode: u-xs,g-xwrs,o-xwrt
state: file
with_items:
- '{{ files_found.stdout_lines }}'
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653065
- NIST-800-53-AU-12(b)
- configure_strategy
- file_permissions_etc_audit_rulesd
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | configure |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
find -L /etc/audit/rules.d/ -maxdepth 1 -perm /u+xs,g+xwrs,o+xwrt -type f -regextype posix-extended -regex '^.*rules$' -exec chmod u-xs,g-xwrs,o-xwrt {} \;
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
|
Ensure the default plugins for the audit dispatcher are Installed
[ref]ruleThe audit-audispd-plugins package should be installed. Rationale:Information stored in one location is vulnerable to accidental or incidental deletion or alteration. Off-loading is a common process in information systems with limited audit storage capacity. References:
CCI-001851, 164.308(a)(1)(ii)(D), 164.308(a)(5)(ii)(C), 164.310(a)(2)(iv), 164.310(d)(2)(iii), 164.312(b), Req-10.5.3, 10.3.3, 10.3, SRG-OS-000342-GPOS-00133, UBTU-22-653020, SV-260592r958754_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-653020
- PCI-DSS-Req-10.5.3
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.3
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_audit-audispd-plugins_installed
- name: Ensure audispd-plugins is installed
package:
name: audispd-plugins
state: present
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653020
- PCI-DSS-Req-10.5.3
- PCI-DSSv4-10.3
- PCI-DSSv4-10.3.3
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_audit-audispd-plugins_installed
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_audispd-plugins
class install_audispd-plugins {
package { 'audispd-plugins':
ensure => 'installed',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "audispd-plugins"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[[packages]]
name = "audispd-plugins"
version = "*"
|
Ensure the audit Subsystem is Installed
[ref]ruleThe audit package should be installed. Rationale:The auditd service is an access monitoring and accounting daemon, watching system calls to audit any access, in comparison with potential local access control policy such as SELinux policy. References:
R33, R73, 4.1.1.1, CCI-000133, CCI-001881, CCI-001875, CCI-000154, CCI-001882, CCI-000158, CCI-001914, CCI-000169, CCI-001464, CCI-001878, CCI-001877, CCI-001889, CCI-000135, CCI-002884, CCI-001487, CCI-003938, CCI-000132, CCI-000134, CCI-000172, CCI-000130, CCI-000131, CCI-001879, CCI-001880, CCI-001876, CCI-000159, 164.308(a)(1)(ii)(D), 164.308(a)(5)(ii)(C), 164.310(a)(2)(iv), 164.310(d)(2)(iii), 164.312(b), CIP-004-6 R3.3, CIP-007-3 R6.5, AC-7(a), AU-7(1), AU-7(2), AU-14, AU-12(2), AU-2(a), CM-6(a), FAU_GEN.1, Req-10.1, 10.2.1, 10.2, SRG-OS-000062-GPOS-00031, SRG-OS-000037-GPOS-00015, SRG-OS-000038-GPOS-00016, SRG-OS-000039-GPOS-00017, SRG-OS-000040-GPOS-00018, SRG-OS-000041-GPOS-00019, SRG-OS-000042-GPOS-00021, SRG-OS-000051-GPOS-00024, SRG-OS-000054-GPOS-00025, SRG-OS-000122-GPOS-00063, SRG-OS-000254-GPOS-00095, SRG-OS-000255-GPOS-00096, SRG-OS-000337-GPOS-00129, SRG-OS-000348-GPOS-00136, SRG-OS-000349-GPOS-00137, SRG-OS-000350-GPOS-00138, SRG-OS-000351-GPOS-00139, SRG-OS-000352-GPOS-00140, SRG-OS-000353-GPOS-00141, SRG-OS-000354-GPOS-00142, SRG-OS-000358-GPOS-00145, SRG-OS-000365-GPOS-00152, SRG-OS-000392-GPOS-00172, SRG-OS-000475-GPOS-00220, UBTU-22-653010, SV-260590r986296_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- DISA-STIG-UBTU-22-653010
- NIST-800-53-AC-7(a)
- NIST-800-53-AU-12(2)
- NIST-800-53-AU-14
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-7(1)
- NIST-800-53-AU-7(2)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.1
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_audit_installed
- name: Ensure auditd is installed
package:
name: auditd
state: present
when: '"linux-base" in ansible_facts.packages'
tags:
- DISA-STIG-UBTU-22-653010
- NIST-800-53-AC-7(a)
- NIST-800-53-AU-12(2)
- NIST-800-53-AU-14
- NIST-800-53-AU-2(a)
- NIST-800-53-AU-7(1)
- NIST-800-53-AU-7(2)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.1
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- package_audit_installed
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include install_auditd
class install_auditd {
package { 'auditd':
ensure => 'installed',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed; then
DEBIAN_FRONTEND=noninteractive apt-get install -y "auditd"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[[packages]]
name = "auditd"
version = "*"
|
Enable auditd Service
[ref]ruleThe auditd service is an essential userspace component of
the Linux Auditing System, as it is responsible for writing audit records to
disk.
The auditd service can be enabled with the following command:
$ sudo systemctl enable auditd.service Rationale:Without establishing what type of events occurred, it would be difficult
to establish, correlate, and investigate the events leading up to an outage or attack.
Ensuring the auditd service is active ensures audit records
generated by the kernel are appropriately recorded.
Additionally, a properly configured audit subsystem ensures that actions of
individual system users can be uniquely traced to those users so they
can be held accountable for their actions. References:
R33, R73, 4.1.1.2, 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.3.1, 3.3.2, 3.3.6, CCI-000133, CCI-001881, CCI-001875, CCI-000154, CCI-001882, CCI-000158, CCI-001914, CCI-000169, CCI-001464, CCI-001878, CCI-001877, CCI-001889, CCI-000135, CCI-002884, CCI-001487, CCI-003938, CCI-000132, CCI-004188, CCI-000134, CCI-000172, CCI-000130, CCI-000131, CCI-001879, CCI-001880, CCI-001876, 164.308(a)(1)(ii)(D), 164.308(a)(5)(ii)(C), 164.310(a)(2)(iv), 164.310(d)(2)(iii), 164.312(b), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, CIP-004-6 R3.3, CIP-007-3 R6.5, AC-2(g), AU-3, AU-10, AU-2(d), AU-12(c), AU-14(1), AC-6(9), CM-6(a), SI-4(23), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1, Req-10.1, 10.2.1, 10.2, SRG-OS-000062-GPOS-00031, SRG-OS-000037-GPOS-00015, SRG-OS-000038-GPOS-00016, SRG-OS-000039-GPOS-00017, SRG-OS-000040-GPOS-00018, SRG-OS-000041-GPOS-00019, SRG-OS-000042-GPOS-00021, SRG-OS-000051-GPOS-00024, SRG-OS-000054-GPOS-00025, SRG-OS-000122-GPOS-00063, SRG-OS-000254-GPOS-00095, SRG-OS-000255-GPOS-00096, SRG-OS-000337-GPOS-00129, SRG-OS-000348-GPOS-00136, SRG-OS-000349-GPOS-00137, SRG-OS-000350-GPOS-00138, SRG-OS-000351-GPOS-00139, SRG-OS-000352-GPOS-00140, SRG-OS-000353-GPOS-00141, SRG-OS-000354-GPOS-00142, SRG-OS-000358-GPOS-00145, SRG-OS-000365-GPOS-00152, SRG-OS-000392-GPOS-00172, SRG-OS-000475-GPOS-00220, SRG-APP-000095-CTR-000170, SRG-APP-000409-CTR-000990, SRG-APP-000508-CTR-001300, SRG-APP-000510-CTR-001310, UBTU-22-653015, SV-260591r986297_rule Remediation Ansible snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-653015
- NIST-800-171-3.3.1
- NIST-800-171-3.3.2
- NIST-800-171-3.3.6
- NIST-800-53-AC-2(g)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-10
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-14(1)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-3
- NIST-800-53-CM-6(a)
- NIST-800-53-SI-4(23)
- PCI-DSS-Req-10.1
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_auditd_enabled
- name: Enable auditd Service - Enable service auditd
block:
- name: Gather the package facts
package_facts:
manager: auto
- name: Enable auditd Service - Enable Service auditd
ansible.builtin.systemd:
name: auditd
enabled: true
state: started
masked: false
when:
- '"auditd" in ansible_facts.packages'
when:
- '"linux-base" in ansible_facts.packages'
- '"auditd" in ansible_facts.packages'
tags:
- CJIS-5.4.1.1
- DISA-STIG-UBTU-22-653015
- NIST-800-171-3.3.1
- NIST-800-171-3.3.2
- NIST-800-171-3.3.6
- NIST-800-53-AC-2(g)
- NIST-800-53-AC-6(9)
- NIST-800-53-AU-10
- NIST-800-53-AU-12(c)
- NIST-800-53-AU-14(1)
- NIST-800-53-AU-2(d)
- NIST-800-53-AU-3
- NIST-800-53-CM-6(a)
- NIST-800-53-SI-4(23)
- PCI-DSS-Req-10.1
- PCI-DSSv4-10.2
- PCI-DSSv4-10.2.1
- enable_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- service_auditd_enabled
Remediation Puppet snippet: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
include enable_auditd
class enable_auditd {
service {'auditd':
enable => true,
ensure => 'running',
}
}
Remediation Shell script: (show)
Complexity: | low |
---|
Disruption: | low |
---|
Reboot: | false |
---|
Strategy: | enable |
---|
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && { dpkg-query --show --showformat='${db:Status-Status}\n' 'auditd' 2>/dev/null | grep -q '^installed'; }; then
SYSTEMCTL_EXEC='/usr/bin/systemctl'
"$SYSTEMCTL_EXEC" unmask 'auditd.service'
if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
"$SYSTEMCTL_EXEC" start 'auditd.service'
fi
"$SYSTEMCTL_EXEC" enable 'auditd.service'
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[customizations.services]
enabled = ["auditd"]
|
Enable Auditing for Processes Which Start Prior to the Audit Daemon
[ref]ruleTo ensure all processes can be audited, even those which start
prior to the audit daemon, add the argument audit=1 to the default
GRUB 2 command line for the Linux operating system.
To ensure that audit=1 is added as a kernel command line
argument to newly installed kernels, add audit=1 to the
default Grub2 command line for Linux operating systems. Modify the line within
/etc/default/grub as shown below:
GRUB_CMDLINE_LINUX="... audit=1 ..."
Run the following command to update command line for already installed kernels:# update-grub Rationale:Each process on the system carries an "auditable" flag which indicates whether
its activities can be audited. Although auditd takes care of enabling
this for all processes which launch after it does, adding the kernel argument
ensures it is set for every process during boot. References:
4.1.1.3, 1, 11, 12, 13, 14, 15, 16, 19, 3, 4, 5, 6, 7, 8, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.02, DSS05.03, DSS05.04, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.3.1, CCI-000172, CCI-000130, CCI-000135, CCI-000169, CCI-002884, CCI-001464, 164.308(a)(1)(ii)(D), 164.308(a)(5)(ii)(C), 164.310(a)(2)(iv), 164.310(d)(2)(iii), 164.312(b), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AC-17(1), AU-14(1), AU-10, CM-6(a), IR-5(1), DE.AE-3, DE.AE-5, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1, Req-10.3, 10.7.2, 10.7, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000473-GPOS-00218, SRG-OS-000254-GPOS-00095, UBTU-22-212015, SV-260471r991555_rule Remediation Shell script: (show)
# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}
' 'linux-base' 2>/dev/null | grep -q ^installed && { dpkg-query --show --showformat='${db:Status-Status}\n' 'grub2-common' 2>/dev/null | grep -q '^installed'; }; then
expected_value="1"
if [[ "$OSCAP_BOOTC_BUILD" == "YES" ]] ; then
KARGS_DIR="/usr/lib/bootc/kargs.d/"
if grep -q -E "audit" "$KARGS_DIR/*.toml" ; then
sed -i -E "s/^(\s*kargs\s*=\s*\[.*)\"audit=[^\"]*\"(.*]\s*)/\1\"audit=$expected_value\"\2/" "$KARGS_DIR/*.toml"
else
echo "kargs = [\"audit=$expected_value\"]" >> "$KARGS_DIR/10-audit.toml"
fi
else
# Correct the form of default kernel command line in GRUB
if grep -q '^\s*GRUB_CMDLINE_LINUX=.*audit=.*"' '/etc/default/grub' ; then
# modify the GRUB command-line if an audit= arg already exists
sed -i "s/\(^\s*GRUB_CMDLINE_LINUX=\".*\)audit=[^[:space:]]\+\(.*\"\)/\1audit=1\2/" '/etc/default/grub'
# Add to already existing GRUB_CMDLINE_LINUX parameters
elif grep -q '^\s*GRUB_CMDLINE_LINUX=' '/etc/default/grub' ; then
# no audit=arg is present, append it
sed -i "s/\(^\s*GRUB_CMDLINE_LINUX=\".*\)\"/\1 audit=1\"/" '/etc/default/grub'
# Add GRUB_CMDLINE_LINUX parameters line
else
echo "GRUB_CMDLINE_LINUX=\"audit=1\"" >> '/etc/default/grub'
fi
update-grub
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Remediation script: (show)
[customizations.kernel]
append = "audit=1"
|