diff --git a/bin/hardening/chrony_is_enabled_and_running.sh b/bin/hardening/chrony_is_enabled_and_running.sh new file mode 100755 index 0000000..084b21a --- /dev/null +++ b/bin/hardening/chrony_is_enabled_and_running.sh @@ -0,0 +1,91 @@ +#!/bin/bash + +# run-shellcheck +# +# CIS Debian Hardening +# + +# +# Ensure chrony is enabled and running (Automated) +# + +set -e # One error, it's over +set -u # One variable unset, it's over + +# shellcheck disable=2034 +HARDENING_LEVEL=3 +# shellcheck disable=2034 +DESCRIPTION="Ensure chrony is enabled and running." +PACKAGE="chrony" +SERVICE="chrony" + +# This function will be called if the script status is on enabled / audit mode +audit() { + CHRONY_INSTALLED=0 + CHRONY_ENABLED=0 + CHRONY_RUNNING=0 + + is_pkg_installed "$PACKAGE" + if [ "$FNRET" -ne 0 ]; then + CHRONY_INSTALLED=1 + crit "chrony is not installed" + fi + # no package, no need to check further + return + + is_service_enabled "$SERVICE" + if [ "$FNRET" -ne 0 ]; then + CHRONY_INSTALLED=1 + crit "chrony is not enabled" + fi + + is_service_active "$SERVICE" + if [ "$FNRET" -ne 0 ]; then + CHRONY_RUNNING=1 + crit "chrony is not running" + fi +} + +# This function will be called if the script status is on enabled mode +apply() { + audit + if [ "$CHRONY_INSTALLED" -eq 1 ]; then + warn "Please install chrony manually to ensure only one time synchronization system is installed" + fi + + if [ "$CHRONY_ENABLED" -eq 1 ]; then + info "Enablign chrony service" + manage_service "enable" "$SERVICE" + fi + + if [ "$CHRONY_RUNNING" -eq 1 ]; then + info "Starting chrony service" + manage_service "start" "$SERVICE" + fi + +} + +# This function will check config parameters required +check_config() { + : +} + +# Source Root Dir Parameter +if [ -r /etc/default/cis-hardening ]; then + # shellcheck source=../../debian/default + . /etc/default/cis-hardening +fi +if [ -z "$CIS_LIB_DIR" ]; then + echo "There is no /etc/default/cis-hardening file nor cis-hardening directory in current environment." + echo "Cannot source CIS_LIB_DIR variable, aborting." + exit 128 +fi + +# Main function, will call the proper functions given the configuration (audit, enabled, disabled) +if [ -r "${CIS_LIB_DIR}"/main.sh ]; then + # shellcheck source=../../lib/main.sh + . "${CIS_LIB_DIR}"/main.sh +else + echo "Cannot find main.sh, have you correctly defined your root directory? Current value is $CIS_LIB_DIR in /etc/default/cis-hardening" + exit 128 +fi diff --git a/bin/hardening/network_services_listening.sh b/bin/hardening/network_services_listening.sh new file mode 100755 index 0000000..061270e --- /dev/null +++ b/bin/hardening/network_services_listening.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +# run-shellcheck +# +# CIS Debian Hardening +# + +# +# Ensure only approved services are listening on a network interface (Manual) +# + +set -e # One error, it's over +set -u # One variable unset, it's over + +# shellcheck disable=2034 +HARDENING_LEVEL=3 +# shellcheck disable=2034 +DESCRIPTION="Ensure only approved services are listening on a network interface" +# socket +# ex: "127.0.0.1:123 0.0.0.0:123" +# we only care about the socket, as there may be different process for a same service +# ex: ntp or chrony for time synchronization +EXCEPTIONS="" + +# This function will be called if the script status is on enabled / audit mode +audit() { + local is_valid=0 + while read i; do + socket=$(echo "$i" | awk '{print $5}') + proc=$(echo "$i" | awk '{print $7}' | awk -F ',' '{print $1}' | sed 's/users:((//') + [ -n "$socket" ] && info -e "$proc listening on \t$socket" + + # output example : + # "ntpd" listening on 127.0.0.1:123 + # "ntpd" listening on 0.0.0.0:123 + + if grep -w "$socket" <<<"$EXCEPTIONS" >/dev/null; then + debug "$socket" is an exception + else + crit "$socket" is not an exception + fi + + done <<<"$($SUDO_CMD ss -plntuH)" + +} + +# This function will be called if the script status is on enabled mode +apply() { + info "This recommendation has to be reviewed and applied manually" +} + +create_config() { + # we try to put as default all services that should be running according to the CIS recommendation + cat < disable the service +# - tftp is not a dependency for another package -> remove the package + +# This function will be called if the script status is on enabled / audit mode +audit() { + # 0 means true in bash + PACKAGE_INSTALLED=1 + PACKAGE_IS_DEPENDENCY=1 + SERVICE_ENABLED=1 + + is_pkg_installed "$PACKAGE" + [ "$FNRET" = 0 ] && PACKAGE_INSTALLED=0 # 0 means true in bash + + is_pkg_a_dependency "$PACKAGE" + # dnsmasq is installed with dnsmasq-base, which + [ "$FNRET" = 0 ] && PACKAGE_IS_DEPENDENCY=0 + + is_service_enabled "$SERVICE" + [ "$FNRET" = 0 ] && SERVICE_ENABLED=0 + + if [ "$PACKAGE_INSTALLED" -eq 0 ] && [ "$PACKAGE_IS_DEPENDENCY" -eq 1 ]; then + crit "$PACKAGE is installed and not a dependency" + elif [ "$PACKAGE_INSTALLED" -eq 0 ] && [ "$PACKAGE_IS_DEPENDENCY" -eq 0 ] && [ "$SERVICE_ENABLED" -eq 0 ]; then + crit "$SERVICE is enabled" + else + ok "$PACKAGE is not in use" + fi +} + +# This function will be called if the script status is on enabled mode +apply() { + audit + if [ "$PACKAGE_INSTALLED" -eq 0 ] && [ "$PACKAGE_IS_DEPENDENCY" -eq 1 ]; then + crit "$PACKAGE is installed and not a dependency, removing it" + apt_remove "$PACKAGE" -y + apt-get autoremove -y + elif [ "$PACKAGE_INSTALLED" -eq 0 ] && [ "$PACKAGE_IS_DEPENDENCY" -eq 0 ] && [ "$SERVICE_ENABLED" -eq 0 ] && [ "$IS_CONTAINER" -eq 1 ]; then + crit "$SERVICE is enabled, i'm going to stop and mask it" + systemctl stop "$SERVICE" + systemctl mask "$SERVICE" + else + ok "$PACKAGE is not in use" + fi +} + +# This function will check config parameters required +check_config() { + : +} + +# Source Root Dir Parameter +if [ -r /etc/default/cis-hardening ]; then + # shellcheck source=../../debian/default + . /etc/default/cis-hardening +fi +if [ -z "$CIS_LIB_DIR" ]; then + echo "There is no /etc/default/cis-hardening file nor cis-hardening directory in current environment." + echo "Cannot source CIS_LIB_DIR variable, aborting." + exit 128 +fi + +# Main function, will call the proper functions given the configuration (audit, enabled, disabled) +if [ -r "${CIS_LIB_DIR}"/main.sh ]; then + # shellcheck source=../../lib/main.sh + . "${CIS_LIB_DIR}"/main.sh +else + echo "Cannot find main.sh, have you correctly defined your root directory? Current value is $CIS_LIB_DIR in /etc/default/cis-hardening" + exit 128 +fi diff --git a/bin/hardening/use_time_sync.sh b/bin/hardening/use_time_sync.sh index 6adb0f9..d6ab24c 100755 --- a/bin/hardening/use_time_sync.sh +++ b/bin/hardening/use_time_sync.sh @@ -6,7 +6,7 @@ # # -# Ensure time synchronization is in use (Not Scored) +# Ensure a single time synchronization daemon is in use (Automated) # set -e # One error, it's over @@ -15,28 +15,31 @@ set -u # One variable unset, it's over # shellcheck disable=2034 HARDENING_LEVEL=3 # shellcheck disable=2034 -DESCRIPTION="Ensure time synchronization is in use" +DESCRIPTION="Ensure a single time synchronization is in use" PACKAGES="systemd-timesyncd ntp chrony" # This function will be called if the script status is on enabled / audit mode audit() { - FOUND=false + local count=0 for PACKAGE in $PACKAGES; do is_pkg_installed "$PACKAGE" - if [ "$FNRET" = 0 ]; then - ok "Time synchronization is available through $PACKAGE" - FOUND=true + if [ "$FNRET" -eq 0 ]; then + let count=$((count + 1)) fi done - if [ "$FOUND" = false ]; then + if [ "$count" -eq 0 ]; then crit "None of the following time sync packages are installed: $PACKAGES" + elif [ "$count" -gt 1 ]; then + crit "Multiple time sync packages are installed, from $PACKAGES. Pick one and remove the others" + else + info "A single time sync package from $PACKAGES is installed" fi } # This function will be called if the script status is on enabled mode apply() { - : + info "This recommendation has to be reviewed and applied manually" } # This function will check config parameters required diff --git a/cisharden.sudoers b/cisharden.sudoers index 5f35f5a..d606d78 100644 --- a/cisharden.sudoers +++ b/cisharden.sudoers @@ -24,6 +24,7 @@ Cmnd_Alias SCL_CMD = /bin/grep ,\ /sbin/lsmod,\ /sbin/modprobe,\ /usr/sbin/modprobe -n -v*,\ - /usr/sbin/apparmor_status + /usr/sbin/apparmor_status,\ + /usr/bin/ss * cisharden ALL = (root) NOPASSWD: SCL_CMD diff --git a/debian/control b/debian/control index bacd8d0..1388ba0 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Vcs-Browser: https://github.com/ovh/debian-cis/ Package: cis-hardening Architecture: all -Depends: ${misc:Depends}, patch, coreutils +Depends: ${misc:Depends}, patch, coreutils, iproute2 Description: Suite of configurable scripts to audit or harden a Debian. Modular Debian security hardening scripts based on cisecurity.org ⟨cisecurity.org⟩ recommendations. We use it at OVH ⟨https://www.ovh.com⟩ to diff --git a/lib/utils.sh b/lib/utils.sh index 87594e1..8c1feaf 100644 --- a/lib/utils.sh +++ b/lib/utils.sh @@ -575,6 +575,12 @@ apt_install() { FNRET=0 } +apt_remove() { + local PACKAGE=$1 + DEBIAN_FRONTEND='noninteractive' apt remove -y "$PACKAGE" + FNRET=0 +} + # # Returns if a package is installed # diff --git a/tests/docker/Dockerfile.debian11 b/tests/docker/Dockerfile.debian11 index 277e4ba..87a71ea 100644 --- a/tests/docker/Dockerfile.debian11 +++ b/tests/docker/Dockerfile.debian11 @@ -7,7 +7,7 @@ LABEL description="This image is used to run tests" RUN groupadd -g 500 secaudit && useradd -u 500 -g 500 -s /bin/bash secaudit && install -m 700 -o secaudit -g secaudit -d /home/secaudit -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y openssh-server sudo syslog-ng net-tools auditd cron +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y openssh-server sudo syslog-ng net-tools auditd cron iproute2 COPY --chown=500:500 . /opt/debian-cis/ diff --git a/tests/docker/Dockerfile.debian12 b/tests/docker/Dockerfile.debian12 index f10be1c..76e388e 100644 --- a/tests/docker/Dockerfile.debian12 +++ b/tests/docker/Dockerfile.debian12 @@ -7,7 +7,7 @@ LABEL description="This image is used to run tests" RUN groupadd -g 500 secaudit && useradd -u 500 -g 500 -s /bin/bash secaudit && install -m 700 -o secaudit -g secaudit -d /home/secaudit -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y openssh-server sudo syslog-ng net-tools auditd cron +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y openssh-server sudo syslog-ng net-tools auditd cron iproute2 COPY --chown=500:500 . /opt/debian-cis/ diff --git a/tests/hardening/chrony_is_enabled_and_running.sh b/tests/hardening/chrony_is_enabled_and_running.sh new file mode 100644 index 0000000..b42b002 --- /dev/null +++ b/tests/hardening/chrony_is_enabled_and_running.sh @@ -0,0 +1,18 @@ +# shellcheck shell=bash +# run-shellcheck +test_audit() { + + describe Ensure package is installed + + # install dependencies + apt update + apt install -y chrony + + # not much to test here, we are running in a container, we wont check service state + describe Checking blank host + register_test retvalshouldbe 0 + run blank "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + apt remove -y chrony + +} diff --git a/tests/hardening/network_services_listening.sh b/tests/hardening/network_services_listening.sh new file mode 100644 index 0000000..df2ce1b --- /dev/null +++ b/tests/hardening/network_services_listening.sh @@ -0,0 +1,33 @@ +# shellcheck shell=bash +# run-shellcheck +test_audit() { + describe Prepare exceptions tests + apt install -y netcat-traditional + timeout 5s nc -lp 123 | true & + + describe Running succesfull check + register_test retvalshouldbe 0 + # shellcheck disable=2154 + run success "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + describe Prepare on purpose failing tests + timeout 5s nc -lp 80 | true & + + describe Running failed check + register_test retvalshouldbe 1 + # shellcheck disable=2154 + run failed "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + describe correcting situation + # just wait for timeout to expire + sleep 5 + + describe Checking resolved state + register_test retvalshouldbe 0 + run resolved "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + describe clean installation + apt remove -y netcat-traditional + apt autoremove -y + +} diff --git a/tests/hardening/tftp_is_disabled.sh b/tests/hardening/tftp_is_disabled.sh new file mode 100644 index 0000000..33fe314 --- /dev/null +++ b/tests/hardening/tftp_is_disabled.sh @@ -0,0 +1,36 @@ +# shellcheck shell=bash +# run-shellcheck +test_audit() { + + describe Prepare on purpose failed test + apt install -y tftpd-hpa + # running on a container, will can only test the package installation, not the service management + + describe Running on purpose failed test + register_test retvalshouldbe 1 + # shellcheck disable=2154 + run failed "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + describe correcting situation + sed -i 's/audit/enabled/' "${CIS_CONF_DIR}/conf.d/${script}.cfg" + "${CIS_CHECKS_DIR}/${script}.sh" --apply || true + + describe Checking resolved state + register_test retvalshouldbe 0 + run resolved "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + describe Prepare test package dependencies + # try to install a package that depends on 'tftpd-hpa' + apt install -y tftp-hpa-dbg + # running on a container, we can only test the package installation, not the service management + + describe Running successfull test + register_test retvalshouldbe 0 + # shellcheck disable=2154 + run blank "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + describe clean installation + apt remove -y tftp-hpa-dbg tftpd-hpa + apt autoremove -y + +} diff --git a/tests/hardening/use_time_sync.sh b/tests/hardening/use_time_sync.sh index dc32b03..6076ff7 100644 --- a/tests/hardening/use_time_sync.sh +++ b/tests/hardening/use_time_sync.sh @@ -3,7 +3,6 @@ test_audit() { describe Running on blank host register_test retvalshouldbe 1 - dismiss_count_for_test # shellcheck disable=2154 run blank "${CIS_CHECKS_DIR}/${script}.sh" --audit-all @@ -14,6 +13,11 @@ test_audit() { # Finally assess that your corrective actions end up with a compliant system describe Checking resolved state register_test retvalshouldbe 0 - register_test contain "Time synchronization is available through" run resolved "${CIS_CHECKS_DIR}/${script}.sh" --audit-all + + # we can not check the presence of multiple time synchronization from debian packages, as they are mutually exclusive + describe clean installation + apt remove -y ntp + apt autoremove -y + }