diff --git a/AUTHORS b/AUTHORS index 0934611..e46d827 100644 --- a/AUTHORS +++ b/AUTHORS @@ -2,6 +2,7 @@ Contributors of this project : Developers : Thibault Dewailly, OVH + Stéphane Lesimple, OVH Debian package maintainers : Kevin Tanguy, OVH diff --git a/bin/hardening.sh b/bin/hardening.sh index 573fe26..d6291a0 100755 --- a/bin/hardening.sh +++ b/bin/hardening.sh @@ -20,11 +20,13 @@ AUDIT=0 APPLY=0 AUDIT_ALL=0 AUDIT_ALL_ENABLE_PASSED=0 +ALLOW_SERVICE_LIST=0 +SET_HARDENING_LEVEL=0 CIS_ROOT_DIR='' usage() { cat << EOF -$LONG_SCRIPT_NAME RUN_MODE, where RUN_MODE is one of: +$LONG_SCRIPT_NAME [OPTIONS], where RUN_MODE is one of: --help -h Show this help @@ -53,6 +55,35 @@ $LONG_SCRIPT_NAME RUN_MODE, where RUN_MODE is one of: Don't run this if you have already customized the scripts enable/disable configurations, obviously. + --set-hardening-level + Modifies the configuration to enable/disable tests given an hardening level, + between 1 to 5. Don't run this if you have already customized the scripts + enable/disable configurations. + 1: very basic policy, failure to pass tests at this level indicates severe + misconfiguration of the machine that can have a huge security impact + 2: basic policy, some good practice rules that, once applied, shouldn't + break anything on most systems + 3: best practices policy, passing all tests might need some configuration + modifications (such as specific partitioning, etc.) + 4: high security policy, passing all tests might be time-consuming and + require high adaptation of your workflow + 5: placebo, policy rules that might be very difficult to apply and maintain, + with questionable security benefits + + --allow-service + Use with --set-hardening-level. + Modifies the policy to allow a certain kind of services on the machine, such + as http, mail, etc. Can be specified multiple times to allow multiple services. + Use --allow-service-list to get a list of supported services. + +OPTIONS: + + --only + Modifies the RUN_MODE to only work on the test_number script. + Can be specified multiple times to work only on several scripts. + The test number is the numbered prefix of the script, + i.e. the test number of 1.2_script_name.sh is 1.2. + EOF exit 0 } @@ -61,6 +92,8 @@ if [ $# = 0 ]; then usage fi +declare -a TEST_LIST ALLOWED_SERVICES_LIST + # Arguments parsing while [[ $# > 0 ]]; do ARG="$1" @@ -77,6 +110,21 @@ while [[ $# > 0 ]]; do --apply) APPLY=1 ;; + --allow-service-list) + ALLOW_SERVICE_LIST=1 + ;; + --allow-service) + ALLOWED_SERVICES_LIST[${#ALLOWED_SERVICES_LIST[@]}]="$2" + shift + ;; + --set-hardening-level) + SET_HARDENING_LEVEL="$2" + shift + ;; + --only) + TEST_LIST[${#TEST_LIST[@]}]="$2" + shift + ;; -h|--help) usage ;; @@ -104,8 +152,51 @@ fi [ -r $CIS_ROOT_DIR/lib/common.sh ] && . $CIS_ROOT_DIR/lib/common.sh [ -r $CIS_ROOT_DIR/lib/utils.sh ] && . $CIS_ROOT_DIR/lib/utils.sh +# If --allow-service-list is specified, don't run anything, just list the supported services +if [ "$ALLOW_SERVICE_LIST" = 1 ] ; then + declare -a HARDENING_EXCEPTIONS_LIST + for SCRIPT in $(ls $CIS_ROOT_DIR/bin/hardening/*.sh -v); do + template=$(grep "^HARDENING_EXCEPTION=" "$SCRIPT" | cut -d= -f2) + [ -n "$template" ] && HARDENING_EXCEPTIONS_LIST[${#HARDENING_EXCEPTIONS_LIST[@]}]="$template" + done + echo "Supported services are: "$(echo "${HARDENING_EXCEPTIONS_LIST[@]}" | tr " " "\n" | sort -u | tr "\n" " ") + exit 0 +fi + +# If --set-hardening-level is specified, don't run anything, just apply config for each script +if [ -n "$SET_HARDENING_LEVEL" -a "$SET_HARDENING_LEVEL" != 0 ] ; then + if ! grep -q "^[12345]$" <<< "$SET_HARDENING_LEVEL" ; then + echo "Bad --set-hardening-level specified ('$SET_HARDENING_LEVEL'), expected 1 to 5" + exit 1 + fi + + for SCRIPT in $(ls $CIS_ROOT_DIR/bin/hardening/*.sh -v); do + SCRIPT_BASENAME=$(basename $SCRIPT .sh) + script_level=$(grep "^HARDENING_LEVEL=" "$SCRIPT" | cut -d= -f2) + if [ -z "$script_level" ] ; then + echo "The script $SCRIPT_BASENAME doesn't have a hardening level, configuration untouched for it" + continue + fi + wantedstatus=disabled + [ "$script_level" -le "$SET_HARDENING_LEVEL" ] && wantedstatus=enabled + sed -i -re "s/^status=.+/status=$wantedstatus/" $CIS_ROOT_DIR/etc/conf.d/$SCRIPT_BASENAME.cfg + done + echo "Configuration modified to enable scripts for hardening level at or below $SET_HARDENING_LEVEL" + exit 0 +fi + # Parse every scripts and execute them in the required mode for SCRIPT in $(ls $CIS_ROOT_DIR/bin/hardening/*.sh -v); do + if [ ${#TEST_LIST[@]} -gt 0 ] ; then + # --only X has been specified at least once, is this script in my list ? + SCRIPT_PREFIX=$(grep -Eo '^[0-9.]+' <<< "$(basename $SCRIPT)") + SCRIPT_PREFIX_RE=$(sed -e 's/\./\\./g' <<< "$SCRIPT_PREFIX") + if ! grep -qEw "$SCRIPT_PREFIX_RE" <<< "${TEST_LIST[@]}"; then + # not in the list + continue + fi + fi + info "Treating $SCRIPT" if [ $AUDIT = 1 ]; then diff --git a/bin/hardening/1.1_install_updates.sh b/bin/hardening/1.1_install_updates.sh index 1f8dad9..657dc79 100755 --- a/bin/hardening/1.1_install_updates.sh +++ b/bin/hardening/1.1_install_updates.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=3 + # This function will be called if the script status is on enabled / audit mode audit () { info "Checking if apt needs an update" diff --git a/bin/hardening/10.1.1_set_password_exp_days.sh b/bin/hardening/10.1.1_set_password_exp_days.sh index a1e4d60..ce7dcbc 100755 --- a/bin/hardening/10.1.1_set_password_exp_days.sh +++ b/bin/hardening/10.1.1_set_password_exp_days.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=3 + PACKAGE='login' OPTIONS='PASS_MAX_DAYS=90' FILE='/etc/login.defs' diff --git a/bin/hardening/10.1.2_set_password_min_days_change.sh b/bin/hardening/10.1.2_set_password_min_days_change.sh index 48e9190..a4eef31 100755 --- a/bin/hardening/10.1.2_set_password_min_days_change.sh +++ b/bin/hardening/10.1.2_set_password_min_days_change.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=3 + PACKAGE='login' OPTIONS='PASS_MIN_DAYS=7' FILE='/etc/login.defs' diff --git a/bin/hardening/10.1.3_set_password_exp_warning_days.sh b/bin/hardening/10.1.3_set_password_exp_warning_days.sh index 6a45639..3ff35c1 100755 --- a/bin/hardening/10.1.3_set_password_exp_warning_days.sh +++ b/bin/hardening/10.1.3_set_password_exp_warning_days.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=3 + PACKAGE='login' OPTIONS='PASS_WARN_AGE=7' FILE='/etc/login.defs' diff --git a/bin/hardening/10.2_disable_system_accounts.sh b/bin/hardening/10.2_disable_system_accounts.sh index 6b6667e..0dcb6a9 100755 --- a/bin/hardening/10.2_disable_system_accounts.sh +++ b/bin/hardening/10.2_disable_system_accounts.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=3 + SHELL='/bin/false' FILE='/etc/passwd' RESULT='' @@ -70,6 +72,15 @@ apply () { fi } +# This function will create the config file for this check with default values +create_config() { + cat <=1000 -F auid!=4294967295 \ diff --git a/bin/hardening/8.1.13_record_successful_mount.sh b/bin/hardening/8.1.13_record_successful_mount.sh index c3e4ff7..8f5826a 100755 --- a/bin/hardening/8.1.13_record_successful_mount.sh +++ b/bin/hardening/8.1.13_record_successful_mount.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=4 + AUDIT_PARAMS='-a always,exit -F arch=b64 -S mount -F auid>=1000 -F auid!=4294967295 -k mounts -a always,exit -F arch=b32 -S mount -F auid>=1000 -F auid!=4294967295 -k mounts' FILE='/etc/audit/audit.rules' diff --git a/bin/hardening/8.1.14_record_file_deletions.sh b/bin/hardening/8.1.14_record_file_deletions.sh index 1488d99..6b5c476 100755 --- a/bin/hardening/8.1.14_record_file_deletions.sh +++ b/bin/hardening/8.1.14_record_file_deletions.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=4 + AUDIT_PARAMS='-a always,exit -F arch=b64 -S unlink -S unlinkat -S rename -S renameat -F auid>=1000 -F auid!=4294967295 -k delete -a always,exit -F arch=b32 -S unlink -S unlinkat -S rename -S renameat -F auid>=1000 -F auid!=4294967295 -k delete' FILE='/etc/audit/audit.rules' diff --git a/bin/hardening/8.1.15_record_sudoers_edit.sh b/bin/hardening/8.1.15_record_sudoers_edit.sh index f8740b3..64c1cb5 100755 --- a/bin/hardening/8.1.15_record_sudoers_edit.sh +++ b/bin/hardening/8.1.15_record_sudoers_edit.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=4 + AUDIT_PARAMS='-w /etc/sudoers -p wa -k sudoers -w /etc/sudoers.d/ -p wa -k sudoers' FILE='/etc/audit/audit.rules' diff --git a/bin/hardening/8.1.16_record_sudo_usage.sh b/bin/hardening/8.1.16_record_sudo_usage.sh index c164727..b0e8a74 100755 --- a/bin/hardening/8.1.16_record_sudo_usage.sh +++ b/bin/hardening/8.1.16_record_sudo_usage.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=4 + AUDIT_PARAMS='-w /var/log/auth.log -p wa -k sudoaction' FILE='/etc/audit/audit.rules' diff --git a/bin/hardening/8.1.17_record_kernel_modules.sh b/bin/hardening/8.1.17_record_kernel_modules.sh index 2904209..f4500c3 100755 --- a/bin/hardening/8.1.17_record_kernel_modules.sh +++ b/bin/hardening/8.1.17_record_kernel_modules.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=4 + AUDIT_PARAMS='-w /sbin/insmod -p x -k modules -w /sbin/rmmod -p x -k modules -w /sbin/modprobe -p x -k modules diff --git a/bin/hardening/8.1.18_freeze_auditd_conf.sh b/bin/hardening/8.1.18_freeze_auditd_conf.sh index 0a3df10..4fa408e 100755 --- a/bin/hardening/8.1.18_freeze_auditd_conf.sh +++ b/bin/hardening/8.1.18_freeze_auditd_conf.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=4 + AUDIT_PARAMS='-e 2' FILE='/etc/audit/audit.rules' diff --git a/bin/hardening/8.1.2_enable_auditd.sh b/bin/hardening/8.1.2_enable_auditd.sh index 1c01bf9..50926b7 100755 --- a/bin/hardening/8.1.2_enable_auditd.sh +++ b/bin/hardening/8.1.2_enable_auditd.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=4 + PACKAGE='auditd' SERVICE_NAME='auditd' diff --git a/bin/hardening/8.1.3_audit_bootloader.sh b/bin/hardening/8.1.3_audit_bootloader.sh index b5a7518..d1ef1e9 100755 --- a/bin/hardening/8.1.3_audit_bootloader.sh +++ b/bin/hardening/8.1.3_audit_bootloader.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=4 + FILE='/etc/default/grub' OPTIONS='GRUB_CMDLINE_LINUX="audit=1"' diff --git a/bin/hardening/8.1.4_record_date_time_edit.sh b/bin/hardening/8.1.4_record_date_time_edit.sh index e5d62f9..113777f 100755 --- a/bin/hardening/8.1.4_record_date_time_edit.sh +++ b/bin/hardening/8.1.4_record_date_time_edit.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=4 + AUDIT_PARAMS='-a always,exit -F arch=b64 -S adjtimex -S settimeofday -k time-change -a always,exit -F arch=b32 -S adjtimex -S settimeofday -S stime -k time-change -a always,exit -F arch=b64 -S clock_settime -k time-change diff --git a/bin/hardening/8.1.5_record_user_group_edit.sh b/bin/hardening/8.1.5_record_user_group_edit.sh index ffbfea5..46d6adf 100755 --- a/bin/hardening/8.1.5_record_user_group_edit.sh +++ b/bin/hardening/8.1.5_record_user_group_edit.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=4 + AUDIT_PARAMS='-w /etc/group -p wa -k identity -w /etc/passwd -p wa -k identity -w /etc/gshadow -p wa -k identity diff --git a/bin/hardening/8.1.6_record_network_edit.sh b/bin/hardening/8.1.6_record_network_edit.sh index e8d8a1d..0d3583e 100755 --- a/bin/hardening/8.1.6_record_network_edit.sh +++ b/bin/hardening/8.1.6_record_network_edit.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=4 + AUDIT_PARAMS='-a exit,always -F arch=b64 -S sethostname -S setdomainname -k system-locale -a exit,always -F arch=b32 -S sethostname -S setdomainname -k system-locale -w /etc/issue -p wa -k system-locale diff --git a/bin/hardening/8.1.7_record_mac_edit.sh b/bin/hardening/8.1.7_record_mac_edit.sh index 9c194f5..4fa59a4 100755 --- a/bin/hardening/8.1.7_record_mac_edit.sh +++ b/bin/hardening/8.1.7_record_mac_edit.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=4 + AUDIT_PARAMS='-w /etc/selinux/ -p wa -k MAC-policy' FILE='/etc/audit/audit.rules' diff --git a/bin/hardening/8.1.8_record_login_logout.sh b/bin/hardening/8.1.8_record_login_logout.sh index 95dea18..70572f4 100755 --- a/bin/hardening/8.1.8_record_login_logout.sh +++ b/bin/hardening/8.1.8_record_login_logout.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=4 + AUDIT_PARAMS='-w /var/log/faillog -p wa -k logins -w /var/log/lastlog -p wa -k logins -w /var/log/tallylog -p wa -k logins' diff --git a/bin/hardening/8.1.9_record_session_init.sh b/bin/hardening/8.1.9_record_session_init.sh index 8fa6a01..e3774d1 100755 --- a/bin/hardening/8.1.9_record_session_init.sh +++ b/bin/hardening/8.1.9_record_session_init.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=4 + AUDIT_PARAMS='-w /var/run/utmp -p wa -k session -w /var/log/wtmp -p wa -k session -w /var/log/btmp -p wa -k session' diff --git a/bin/hardening/8.2.1_install_syslog-ng.sh b/bin/hardening/8.2.1_install_syslog-ng.sh index 53a4438..03b41a9 100755 --- a/bin/hardening/8.2.1_install_syslog-ng.sh +++ b/bin/hardening/8.2.1_install_syslog-ng.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=3 + # NB : in CIS, rsyslog has been chosen, however we chose syslog-ng PACKAGE='syslog-ng' diff --git a/bin/hardening/8.2.2_enable_syslog-ng.sh b/bin/hardening/8.2.2_enable_syslog-ng.sh index b59170c..930eefa 100755 --- a/bin/hardening/8.2.2_enable_syslog-ng.sh +++ b/bin/hardening/8.2.2_enable_syslog-ng.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=3 + SERVICE_NAME="syslog-ng" # This function will be called if the script status is on enabled / audit mode diff --git a/bin/hardening/8.2.3_configure_syslog-ng.sh b/bin/hardening/8.2.3_configure_syslog-ng.sh index f57561f..d7ebffa 100755 --- a/bin/hardening/8.2.3_configure_syslog-ng.sh +++ b/bin/hardening/8.2.3_configure_syslog-ng.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=3 + SERVICE_NAME="syslog-ng" # This function will be called if the script status is on enabled / audit mode diff --git a/bin/hardening/8.2.4_set_logfile_perm.sh b/bin/hardening/8.2.4_set_logfile_perm.sh index 58453d1..8796eba 100755 --- a/bin/hardening/8.2.4_set_logfile_perm.sh +++ b/bin/hardening/8.2.4_set_logfile_perm.sh @@ -11,6 +11,8 @@ set -e # One error, it's over set -u # One variable unset, it's over +HARDENING_LEVEL=3 + PERMISSIONS='640' USER='root' GROUP='adm' @@ -64,6 +66,14 @@ apply () { done } +# This function will create the config file for this check with default values +create_config() { + cat < 0 ]]; do ARG="$1" case $ARG in --audit-all) debug "Audit all specified, setting status to audit regardless of configuration" - status=audit + forcedstatus=auditall ;; --audit) - if [ $status != 'disabled' -a $status != 'false' ]; then + if [ "$status" != 'disabled' -a "$status" != 'false' ]; then debug "Audit argument detected, setting status to audit" - status=audit + forcedstatus=audit else info "Audit argument passed but script is disabled" fi @@ -45,6 +38,39 @@ while [[ $# > 0 ]]; do shift done +# Source specific configuration file +if ! [ -r $CIS_ROOT_DIR/etc/conf.d/$SCRIPT_NAME.cfg ] ; then + # If it doesn't exist, create it with default values + echo "# Configuration for $SCRIPT_NAME, created from default values on `date`" > $CIS_ROOT_DIR/etc/conf.d/$SCRIPT_NAME.cfg + # If create_config is a defined function, execute it. + # Otherwise, just disable the test by default. + if type -t create_config | grep -qw function ; then + create_config >> $CIS_ROOT_DIR/etc/conf.d/$SCRIPT_NAME.cfg + else + echo "status=disabled" >> $CIS_ROOT_DIR/etc/conf.d/$SCRIPT_NAME.cfg + fi +fi +[ -r $CIS_ROOT_DIR/etc/conf.d/$SCRIPT_NAME.cfg ] && . $CIS_ROOT_DIR/etc/conf.d/$SCRIPT_NAME.cfg + +# Now check configured value for status, and potential cmdline parameter +if [ "$forcedstatus" = "auditall" ] ; then + # We want to audit even disabled script, so override config value in any case + status=audit +elif [ "$forcedstatus" = "audit" ] ; then + # We want to audit only enabled scripts + if [ "$status" != 'disabled' -a "$status" != 'false' ]; then + debug "Audit argument detected, setting status to audit" + status=audit + else + info "Audit argument passed but script is disabled" + fi +fi + +if [ -z $status ]; then + crit "Could not find status variable for $SCRIPT_NAME, considered as disabled" + exit 2 +fi + case $status in enabled | true ) info "Checking Configuration" diff --git a/lib/utils.sh b/lib/utils.sh index 579b961..71278aa 100644 --- a/lib/utils.sh +++ b/lib/utils.sh @@ -204,9 +204,17 @@ is_service_enabled() { # is_kernel_option_enabled() { - local KERNEL_OPTION=$1 - RESULT=$(zgrep -i $KERNEL_OPTION /proc/config.gz | grep -vE "^#") || : - ANSWER=$(cut -d = -f 2 <<< $RESULT) + local KERNEL_OPTION="$1" + local MODULE_NAME="" + if [ $# -ge 2 ] ; then + MODULE_NAME="$2" + fi + if [ -r "/proc/config.gz" ] ; then + RESULT=$(zgrep "^$KERNEL_OPTION=" /proc/config.gz) || : + elif [ -r "/boot/config-$(uname -r)" ] ; then + RESULT=$(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 @@ -217,6 +225,25 @@ is_kernel_option_enabled() { debug "Kernel option $KERNEL_OPTION not found" FNRET=2 # Not found fi + + if [ "$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=$(find "/lib/modules/$(uname -r)/" -type f -name "$MODULE_NAME.ko") + if [ -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 } #