Ensure System Log Files Have Correct Permissions
Description
The file permissions for all log files written by rsyslog
should
be set to 640, or more restrictive. These log files are determined by the
second part of each Rule line in /etc/rsyslog.conf
and typically
all appear in /var/log
. For each log file LOGFILE
referenced in /etc/rsyslog.conf
, run the following command to
inspect the file’s permissions:
If the permissions are not 640 or more restrictive, run the following
command to correct this:
$ sudo chmod 640 *LOGFILE*
"
Rationale
Log files can contain valuable information regarding system
configuration. If the system log files are not protected unauthorized
users could change the logged data, eliminating their forensic value.
Shell script
The following script can be run on the host to remediate the issue.
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
# List of log file paths to be inspected for correct permissions
# \* Primarily inspect log file paths listed in /etc/rsyslog.conf
RSYSLOG\_ETC\_CONFIG="/etc/rsyslog.conf"
# \* And also the log file paths listed after rsyslog's $IncludeConfig directive
# (store the result into array for the case there's shell glob used as value of IncludeConfig)
readarray -t OLD\_INC < <(grep -e "\$IncludeConfig[[:space:]]\+[^[:space:];]\+" /etc/rsyslog.conf | cut -d ' ' -f 2)
readarray -t RSYSLOG\_INCLUDE\_CONFIG < <(for INCPATH in "${OLD\_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)
readarray -t NEW\_INC < <(awk '/)/{f=0} /include\(/{f=1} f{nf=gensub("^(include\\(|\\s\*)file=\"(\\S+)\".\*","\\2",1); if($0!=nf){print nf}}' /etc/rsyslog.conf)
readarray -t RSYSLOG\_INCLUDE < <(for INCPATH in "${NEW\_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)
# Declare an array to hold the final list of different log file paths
declare -a LOG\_FILE\_PATHS
# Array to hold all rsyslog config entries
RSYSLOG\_CONFIGS=()
RSYSLOG\_CONFIGS=("${RSYSLOG\_ETC\_CONFIG}" "${RSYSLOG\_INCLUDE\_CONFIG[@]}" "${RSYSLOG\_INCLUDE[@]}")
# Get full list of files to be checked
# RSYSLOG\_CONFIGS may contain globs such as
# /etc/rsyslog.d/\*.conf /etc/rsyslog.d/\*.frule
# So, loop over the entries in RSYSLOG\_CONFIGS and use find to get the list of included files.
RSYSLOG\_CONFIG\_FILES=()
for ENTRY in "${RSYSLOG\_CONFIGS[@]}"
do
# If directory, rsyslog will search for config files in recursively.
# However, files in hidden sub-directories or hidden files will be ignored.
if [ -d "${ENTRY}" ]
then
readarray -t FINDOUT < <(find "${ENTRY}" -not -path '\*/.\*' -type f)
RSYSLOG\_CONFIG\_FILES+=("${FINDOUT[@]}")
elif [ -f "${ENTRY}" ]
then
RSYSLOG\_CONFIG\_FILES+=("${ENTRY}")
else
echo "Invalid include object: ${ENTRY}"
fi
done
# Browse each file selected above as containing paths of log files
# ('/etc/rsyslog.conf' and '/etc/rsyslog.d/\*.conf' in the default configuration)
for LOG\_FILE in "${RSYSLOG\_CONFIG\_FILES[@]}"
do
# From each of these files extract just particular log file path(s), thus:
# \* Ignore lines starting with space (' '), comment ('#"), or variable syntax ('$') characters,
# \* Ignore empty lines,
# \* Strip quotes and closing brackets from paths.
# \* Ignore paths that match /dev|/etc.\*\.conf, as those are paths, but likely not log files
# \* From the remaining valid rows select only fields constituting a log file path
# Text file column is understood to represent a log file path if and only if all of the
# following are met:
# \* it contains at least one slash '/' character,
# \* it is preceded by space
# \* it doesn't contain space (' '), colon (':'), and semicolon (';') characters
# Search log file for path(s) only in case it exists!
if [[ -f "${LOG\_FILE}" ]]
then
NORMALIZED\_CONFIG\_FILE\_LINES=$(sed -e "/^[#|$]/d" "${LOG\_FILE}")
LINES\_WITH\_PATHS=$(grep '[^/]\*\s\+\S\*/\S\+$' <<< "${NORMALIZED\_CONFIG\_FILE\_LINES}")
FILTERED\_PATHS=$(awk '{if(NF>=2&&($NF~/^\//||$NF~/^-\//)){sub(/^-\//,"/",$NF);print $NF}}' <<< "${LINES\_WITH\_PATHS}")
CLEANED\_PATHS=$(sed -e "s/[\"')]//g; /\\/etc.\*\.conf/d; /\\/dev\\//d" <<< "${FILTERED\_PATHS}")
MATCHED\_ITEMS=$(sed -e "/^$/d" <<< "${CLEANED\_PATHS}")
# Since above sed command might return more than one item (delimited by newline), split
# the particular matches entries into new array specific for this log file
readarray -t ARRAY\_FOR\_LOG\_FILE <<< "$MATCHED\_ITEMS"
# Concatenate the two arrays - previous content of $LOG\_FILE\_PATHS array with
# items from newly created array for this log file
LOG\_FILE\_PATHS+=("${ARRAY\_FOR\_LOG\_FILE[@]}")
# Delete the temporary array
unset ARRAY\_FOR\_LOG\_FILE
fi
done
# Check for RainerScript action log format which might be also multiline so grep regex is a bit
# curly:
# extract possibly multiline action omfile expressions
# extract File="logfile" expression
# match only "logfile" expression
for LOG\_FILE in "${RSYSLOG\_CONFIG\_FILES[@]}"
do
ACTION\_OMFILE\_LINES=$(grep -ozP "action\s\*\(\s\*type\s\*=\s\*\"omfile\"[^\)]\*\)" "${LOG\_FILE}")
OMFILE\_LINES=$(echo "${ACTION\_OMFILE\_LINES}"| grep -aoP "File\s\*=\s\*\"([/[:alnum:][:punct:]]\*)\"\s\*\)")
LOG\_FILE\_PATHS+=("$(echo "${OMFILE\_LINES}"| grep -oE "\"([/[:alnum:][:punct:]]\*)\""|tr -d "\"")")
done
# Ensure the correct attribute if file exists
FILE\_CMD="chmod"
for LOG\_FILE\_PATH in "${LOG\_FILE\_PATHS[@]}"
do
# Sanity check - if particular $LOG\_FILE\_PATH is empty string, skip it from further processing
if [ -z "$LOG\_FILE\_PATH" ]
then
continue
fi
$FILE\_CMD "0640" "$LOG\_FILE\_PATH"
done
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi
Ansible playbook
The following playbook can be run with Ansible to remediate the issue.
- name: Ensure System Log Files Have Correct Permissions - Set rsyslog logfile configuration
facts
ansible.builtin.set\_fact:
rsyslog\_etc\_config: /etc/rsyslog.conf
when: ansible\_virtualization\_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-80191-0
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.1
- PCI-DSS-Req-10.5.2
- PCI-DSSv4-10.3.1
- PCI-DSSv4-10.3.2
- configure\_strategy
- low\_complexity
- medium\_disruption
- medium\_severity
- no\_reboot\_needed
- rsyslog\_files\_permissions
- name: Ensure System Log Files Have Correct Permissions - Get IncludeConfig directive
ansible.builtin.shell: |
set -o pipefail
grep -e '$IncludeConfig' {{ rsyslog\_etc\_config }} | cut -d ' ' -f 2 || true
register: rsyslog\_old\_inc
changed\_when: false
when: ansible\_virtualization\_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-80191-0
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.1
- PCI-DSS-Req-10.5.2
- PCI-DSSv4-10.3.1
- PCI-DSSv4-10.3.2
- configure\_strategy
- low\_complexity
- medium\_disruption
- medium\_severity
- no\_reboot\_needed
- rsyslog\_files\_permissions
- name: Ensure System Log Files Have Correct Permissions - Get include files directives
ansible.builtin.shell: |
set -o pipefail
awk '/)/{f=0} /include\(/{f=1} f{nf=gensub("^(include\\(|\\s\*)file=\"(\\S+)\".\*","\\2",1); if($0!=nf){print nf}}' {{ rsyslog\_etc\_config }} || true
register: rsyslog\_new\_inc
changed\_when: false
when: ansible\_virtualization\_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-80191-0
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.1
- PCI-DSS-Req-10.5.2
- PCI-DSSv4-10.3.1
- PCI-DSSv4-10.3.2
- configure\_strategy
- low\_complexity
- medium\_disruption
- medium\_severity
- no\_reboot\_needed
- rsyslog\_files\_permissions
- name: Ensure System Log Files Have Correct Permissions - Aggregate rsyslog includes
ansible.builtin.set\_fact:
include\_config\_output: '{{ rsyslog\_old\_inc.stdout\_lines + rsyslog\_new\_inc.stdout\_lines
}}'
when: ansible\_virtualization\_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-80191-0
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.1
- PCI-DSS-Req-10.5.2
- PCI-DSSv4-10.3.1
- PCI-DSSv4-10.3.2
- configure\_strategy
- low\_complexity
- medium\_disruption
- medium\_severity
- no\_reboot\_needed
- rsyslog\_files\_permissions
- name: Ensure System Log Files Have Correct Permissions - List all config files
ansible.builtin.find:
paths: '{{ item | dirname }}'
patterns: '{{ item | basename }}'
hidden: false
follow: true
loop: '{{ include\_config\_output | list + [rsyslog\_etc\_config] }}'
register: rsyslog\_config\_files
failed\_when: false
changed\_when: false
when: ansible\_virtualization\_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-80191-0
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.1
- PCI-DSS-Req-10.5.2
- PCI-DSSv4-10.3.1
- PCI-DSSv4-10.3.2
- configure\_strategy
- low\_complexity
- medium\_disruption
- medium\_severity
- no\_reboot\_needed
- rsyslog\_files\_permissions
- name: Ensure System Log Files Have Correct Permissions - Extract log files old format
ansible.builtin.shell: |
set -o pipefail
grep -oP '^[^(\s|#|\$)]+[\s]+.\*[\s]+-?(/+[^:;\s]+);\*\.\*$' {{ item.1.path }} | \
awk '{print $NF}' | \
sed -e 's/^-//' || true
loop: '{{ rsyslog\_config\_files.results | subelements(''files'') }}'
register: log\_files\_old
changed\_when: false
when: ansible\_virtualization\_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-80191-0
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.1
- PCI-DSS-Req-10.5.2
- PCI-DSSv4-10.3.1
- PCI-DSSv4-10.3.2
- configure\_strategy
- low\_complexity
- medium\_disruption
- medium\_severity
- no\_reboot\_needed
- rsyslog\_files\_permissions
- name: Ensure System Log Files Have Correct Permissions - Extract log files new format
ansible.builtin.shell: |
set -o pipefail
grep -ozP "action\s\*\(\s\*type\s\*=\s\*\"omfile\"[^\)]\*\)" {{ item.1.path }} | \
grep -aoP "File\s\*=\s\*\"([/[:alnum:][:punct:]]\*)\"\s\*\)" | \
grep -oE "\"([/[:alnum:][:punct:]]\*)\"" | \
tr -d "\""|| true
loop: '{{ rsyslog\_config\_files.results | subelements(''files'') }}'
register: log\_files\_new
changed\_when: false
when: ansible\_virtualization\_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-80191-0
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.1
- PCI-DSS-Req-10.5.2
- PCI-DSSv4-10.3.1
- PCI-DSSv4-10.3.2
- configure\_strategy
- low\_complexity
- medium\_disruption
- medium\_severity
- no\_reboot\_needed
- rsyslog\_files\_permissions
- name: Ensure System Log Files Have Correct Permissions - Sum all log files found
ansible.builtin.set\_fact:
log\_files: '{{ log\_files\_new.results | map(attribute=''stdout\_lines'') | list
| flatten | unique + log\_files\_old.results | map(attribute=''stdout\_lines'')
| list | flatten | unique }}'
when: ansible\_virtualization\_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-80191-0
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.1
- PCI-DSS-Req-10.5.2
- PCI-DSSv4-10.3.1
- PCI-DSSv4-10.3.2
- configure\_strategy
- low\_complexity
- medium\_disruption
- medium\_severity
- no\_reboot\_needed
- rsyslog\_files\_permissions
- name: Ensure System Log Files Have Correct Permissions -Setup log files attribute
ansible.builtin.file:
path: '{{ item }}'
mode: '0640'
state: file
loop: '{{ log\_files | list | flatten | unique }}'
failed\_when: false
when: ansible\_virtualization\_type not in ["docker", "lxc", "openvz", "podman", "container"]
tags:
- CCE-80191-0
- NIST-800-53-AC-6(1)
- NIST-800-53-CM-6(a)
- PCI-DSS-Req-10.5.1
- PCI-DSS-Req-10.5.2
- PCI-DSSv4-10.3.1
- PCI-DSSv4-10.3.2
- configure\_strategy
- low\_complexity
- medium\_disruption
- medium\_severity
- no\_reboot\_needed
- rsyslog\_files\_permissions