debian-cis/lib/utils.sh
Charles Herlin d2bbf754ac Migrate generic checks from secaudit to cis-hardening
new file:   99.3.1_acc_shadow_sha512.sh
new file:   99.3.2_acc_sudoers_no_all.sh
new file:   99.4_net_fw_default_policy_drop.sh
new file:   99.5.1_ssh_auth_pubk_only.sh
new file:   99.5.2.1_ssh_cry_kex.sh
new file:   99.5.2.2_ssh_cry_mac.sh
new file:   99.5.2.3_ssh_cry_rekey.sh
new file:   99.5.3_ssh_disable_features.sh
new file:   99.5.4_ssh_keys_from.sh
new file:   99.5.5_ssh_strict_modes.sh
new file:   99.5.6_ssh_sys_accept_env.sh
new file:   99.5.7_ssh_sys_no_legacy.sh
new file:   99.5.8_ssh_sys_sandbox.sh
new file:   99.5.9_ssh_log_level.sh

Fix descriptions in comment section for 99.* secaudit checks

Remove duplicated legacy services that are already taken care of by vanilla cis

Enable custom configuration of checks in config-file, no more hard coded conf
Add test to disable check if debian version is too old
Add excused IPs while checking "from" field of authorized_keys
Escaping dots in IPs
Manage Kex for different debian versions
Add tests for generic checks and add apply for ssh config
Apply shellcheck recommendations on audit/hardening scripts
Update script to check for allowed IPs only, remove bastion related
Fill `apply` func for ssh config related scripts
Add and update tests scenarii

Disable shellcheck test for external source 1091

As of today, the entire project is not shellcheck compliant, I prefer
disabling the test that warns about not finding external source (that
arent compliant). I will enable it again when the project library will
be shellchecked
https://github.com/koalaman/shellcheck/wiki/SC1091

Refactor password policy check with one check by feature

Previous file will now only look for bad passwords in /etc/shadow
I added two checks that look for the compliant configuration lines in
conf files /etc/logins.defs and /etc/pam.d/common-passwords

FIX: merge chained sed and fix regex

FIX: update regex to capture more output
FIX: fix pattern to ignore commented lines, add apply

Also add tests to ensure that commented lines are not detected as valid
configuration

CHORE: cleanup test situation with file and users removal
IMP: add case insensitive option when looking for patterns in files
CHORE: removed duplicated line in test file
2019-02-11 18:05:03 +01:00

411 lines
9.8 KiB
Bash

# CIS Debian Hardening Utility functions
#
# Sysctl
#
has_sysctl_param_expected_result() {
local SYSCTL_PARAM=$1
local EXP_RESULT=$2
if [ "$($SUDO_CMD sysctl $SYSCTL_PARAM 2>/dev/null)" = "$SYSCTL_PARAM = $EXP_RESULT" ]; then
FNRET=0
elif [ $? = 255 ]; then
debug "$SYSCTL_PARAM does not exist"
FNRET=255
else
debug "$SYSCTL_PARAM should be set to $EXP_RESULT"
FNRET=1
fi
}
does_sysctl_param_exists() {
local SYSCTL_PARAM=$1
if [ "$($SUDO_CMD sysctl -a 2>/dev/null |grep "$SYSCTL_PARAM" -c)" = 0 ]; then
FNRET=1
else
FNRET=0
fi
}
set_sysctl_param() {
local SYSCTL_PARAM=$1
local VALUE=$2
debug "Setting $SYSCTL_PARAM to $VALUE"
if [ "$(sysctl -w $SYSCTL_PARAM=$VALUE 2>/dev/null)" = "$SYSCTL_PARAM = $VALUE" ]; then
FNRET=0
elif [ $? = 255 ]; then
debug "$SYSCTL_PARAM does not exist"
FNRET=255
else
warn "$SYSCTL_PARAM failed!"
FNRET=1
fi
}
#
# Dmesg
#
does_pattern_exist_in_dmesg() {
local PATTERN=$1
if $($SUDO_CMD dmesg | grep -qE "$PATTERN"); then
FNRET=0
else
FNRET=1
fi
}
#
# File
#
does_file_exist() {
local FILE=$1
if $SUDO_CMD [ -e $FILE ]; then
FNRET=0
else
FNRET=1
fi
}
has_file_correct_ownership() {
local FILE=$1
local USER=$2
local GROUP=$3
local USERID=$(id -u $USER)
local GROUPID=$(getent group $GROUP | cut -d: -f3)
debug "$SUDO_CMD stat -c '%u %g' $FILE"
if [ "$($SUDO_CMD stat -c "%u %g" $FILE)" = "$USERID $GROUPID" ]; then
FNRET=0
else
FNRET=1
fi
}
has_file_correct_permissions() {
local FILE=$1
local PERMISSIONS=$2
if [ $($SUDO_CMD stat -L -c "%a" $1) = "$PERMISSIONS" ]; then
FNRET=0
else
FNRET=1
fi
}
does_pattern_exist_in_file_nocase() {
_does_pattern_exist_in_file "-Ei" $*
}
does_pattern_exist_in_file() {
_does_pattern_exist_in_file "-E" $*
}
_does_pattern_exist_in_file() {
local OPTIONS="$1"
shift
local FILE="$1"
shift
local PATTERN="$*"
debug "Checking if $PATTERN is present in $FILE"
if $SUDO_CMD [ -r "$FILE" ] ; then
debug "$SUDO_CMD grep -q $OPTIONS -- '$PATTERN' $FILE"
if $($SUDO_CMD grep -q $OPTIONS -- "$PATTERN" $FILE); then
FNRET=0
else
FNRET=1
fi
else
debug "File $FILE is not readable!"
FNRET=2
fi
}
add_end_of_file() {
local FILE=$1
local LINE=$2
debug "Adding $LINE at the end of $FILE"
backup_file "$FILE"
echo "$LINE" >> $FILE
}
add_line_file_before_pattern() {
local FILE=$1
local LINE=$2
local PATTERN=$3
backup_file "$FILE"
debug "Inserting $LINE before $PATTERN in $FILE"
PATTERN=$(sed 's@/@\\\/@g' <<< $PATTERN)
debug "sed -i '/$PATTERN/i $LINE' $FILE"
sed -i "/$PATTERN/i $LINE" $FILE
FNRET=0
}
replace_in_file() {
local FILE=$1
local SOURCE=$2
local DESTINATION=$3
backup_file "$FILE"
debug "Replacing $SOURCE to $DESTINATION in $FILE"
SOURCE=$(sed 's@/@\\\/@g' <<< $SOURCE)
debug "sed -i 's/$SOURCE/$DESTINATION/g' $FILE"
sed -i "s/$SOURCE/$DESTINATION/g" $FILE
FNRET=0
}
delete_line_in_file() {
local FILE=$1
local PATTERN=$2
backup_file "$FILE"
debug "Deleting lines from $FILE containing $PATTERN"
PATTERN=$(sed 's@/@\\\/@g' <<< $PATTERN)
debug "sed -i '/$PATTERN/d' $FILE"
sed -i "/$PATTERN/d" $FILE
FNRET=0
}
#
# Users and groups
#
does_user_exist() {
local USER=$1
if $(getent passwd $USER >/dev/null 2>&1); then
FNRET=0
else
FNRET=1
fi
}
does_group_exist() {
local GROUP=$1
if $(getent group $GROUP >/dev/null 2>&1); then
FNRET=0
else
FNRET=1
fi
}
#
# Service Boot Checks
#
is_service_enabled() {
local SERVICE=$1
if [ $($SUDO_CMD find /etc/rc?.d/ -name "S*$SERVICE" -print | wc -l) -gt 0 ]; then
debug "Service $SERVICE is enabled"
FNRET=0
else
debug "Service $SERVICE is disabled"
FNRET=1
fi
}
#
# Kernel Options checks
#
is_kernel_option_enabled() {
local KERNEL_OPTION="$1"
local MODULE_NAME=""
if [ $# -ge 2 ] ; then
MODULE_NAME="$2"
fi
if $SUDO_CMD [ -r "/proc/config.gz" ] ; then
RESULT=$($SUDO_CMD zgrep "^$KERNEL_OPTION=" /proc/config.gz) || :
elif $SUDO_CMD [ -r "/boot/config-$(uname -r)" ] ; then
RESULT=$($SUDO_CMD grep "^$KERNEL_OPTION=" "/boot/config-$(uname -r)") || :
fi
ANSWER=$(cut -d = -f 2 <<< "$RESULT")
if [ "x$ANSWER" = "xy" ]; then
debug "Kernel option $KERNEL_OPTION enabled"
FNRET=0
elif [ "x$ANSWER" = "xn" ]; then
debug "Kernel option $KERNEL_OPTION disabled"
FNRET=1
else
debug "Kernel option $KERNEL_OPTION not found"
FNRET=2 # Not found
fi
if $SUDO_CMD [ "$FNRET" -ne 0 -a -n "$MODULE_NAME" -a -d "/lib/modules/$(uname -r)" ] ; then
# also check in modules, because even if not =y, maybe
# the admin compiled it separately later (or out-of-tree)
# as a module (regardless of the fact that we have =m or not)
debug "Checking if we have $MODULE_NAME.ko"
local modulefile=$($SUDO_CMD find "/lib/modules/$(uname -r)/" -type f -name "$MODULE_NAME.ko")
if $SUDO_CMD [ -n "$modulefile" ] ; then
debug "We do have $modulefile!"
# ... but wait, maybe it's blacklisted? check files in /etc/modprobe.d/ for "blacklist xyz"
if grep -qRE "^\s*blacklist\s+$MODULE_NAME\s*$" /etc/modprobe.d/ ; then
debug "... but it's blacklisted!"
FNRET=1 # Not found (found but blacklisted)
# FIXME: even if blacklisted, it might be present in the initrd and
# be insmod from there... but painful to check :/ maybe lsmod would be enough ?
fi
FNRET=0 # Found!
fi
fi
}
#
# Mounting point
#
# Verify $1 is a partition declared in fstab
is_a_partition() {
local PARTITION_NAME=$1
FNRET=128
if $(grep "[[:space:]]$1[[:space:]]" /etc/fstab | grep -vqE "^#"); then
debug "$PARTITION found in fstab"
FNRET=0
else
debug "Unable to find $PARTITION in fstab"
FNRET=1
fi
}
# Verify that $1 is mounted at runtime
is_mounted() {
local PARTITION_NAME=$1
if $(grep -q "[[:space:]]$1[[:space:]]" /proc/mounts); then
debug "$PARTITION found in /proc/mounts, it's mounted"
FNRET=0
else
debug "Unable to find $PARTITION in /proc/mounts"
FNRET=1
fi
}
# Verify $1 has the proper option $2 in fstab
has_mount_option() {
local PARTITION=$1
local OPTION=$2
if $(grep "[[:space:]]$1[[:space:]]" /etc/fstab | grep -vE "^#" | awk {'print $4'} | grep -q "$2"); then
debug "$OPTION has been detected in fstab for partition $PARTITION"
FNRET=0
else
debug "Unable to find $OPTION in fstab for partition $PARTITION"
FNRET=1
fi
}
# Verify $1 has the proper option $2 at runtime
has_mounted_option() {
local PARTITION=$1
local OPTION=$2
if $(grep "[[:space:]]$1[[:space:]]" /proc/mounts | awk {'print $4'} | grep -q "$2"); then
debug "$OPTION has been detected in /proc/mounts for partition $PARTITION"
FNRET=0
else
debug "Unable to find $OPTION in /proc/mounts for partition $PARTITION"
FNRET=1
fi
}
# Setup mount option in fstab
add_option_to_fstab() {
local PARTITION=$1
local OPTION=$2
debug "Setting $OPTION for $PARTITION in fstab"
backup_file "/etc/fstab"
# For example :
# /dev/sda9 /home ext4 auto,acl,errors=remount-ro 0 2
# /dev/sda9 /home ext4 auto,acl,errors=remount-ro,nodev 0 2
debug "Sed command : sed -ie \"s;\(.*\)\(\s*\)\s\($PARTITION\)\s\(\s*\)\(\w*\)\(\s*\)\(\w*\)*;\1\2 \3 \4\5\6\7,$OPTION;\" /etc/fstab"
sed -ie "s;\(.*\)\(\s*\)\s\($PARTITION\)\s\(\s*\)\(\w*\)\(\s*\)\(\w*\)*;\1\2 \3 \4\5\6\7,$OPTION;" /etc/fstab
}
remount_partition() {
local PARTITION=$1
debug "Remounting $PARTITION"
mount -o remount $PARTITION
}
#
# APT
#
apt_update_if_needed()
{
if [ -e /var/cache/apt/pkgcache.bin ]
then
UPDATE_AGE=$(( $(date +%s) - $(stat -c '%Y' /var/cache/apt/pkgcache.bin) ))
if [ $UPDATE_AGE -gt 21600 ]
then
# update too old, refresh database
$SUDO_CMD apt-get update -y >/dev/null 2>/dev/null
fi
else
$SUDO_CMD apt-get update -y >/dev/null 2>/dev/null
fi
}
apt_check_updates()
{
local NAME="$1"
local DETAILS="/dev/shm/${NAME}"
$SUDO_CMD apt-get upgrade -s 2>/dev/null | grep -E "^Inst" > $DETAILS || :
local COUNT=$(wc -l < "$DETAILS")
FNRET=128 # Unknown function return result
RESULT="" # Result output for upgrade
if [ $COUNT -gt 0 ]; then
RESULT="There is $COUNT updates available :\n$(cat $DETAILS)"
FNRET=1
else
RESULT="OK, no updates available"
FNRET=0
fi
rm $DETAILS
}
apt_install()
{
local PACKAGE=$1
DEBIAN_FRONTEND='noninteractive' apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install $PACKAGE -y
FNRET=0
}
#
# Returns if a package is installed
#
is_pkg_installed()
{
PKG_NAME=$1
if $(dpkg -s $PKG_NAME 2> /dev/null | grep -q '^Status: install ') ; then
debug "$PKG_NAME is installed"
FNRET=0
else
debug "$PKG_NAME is not installed"
FNRET=1
fi
}
# Returns Debian major version
get_debian_major_version()
{
DEB_MAJ_VER=""
does_file_exist /etc/debian_version
if [ $FNRET ]; then
DEB_MAJ_VER=$(cut -d '.' -f1 /etc/debian_version)
else
DEB_MAJ_VER=$(lsb_release -r | cut -f2 | cut -d '.' -f 1)
fi
}