#!/bin/sh ################################################################################# # # Lynis # ------------------ # # Copyright 2007-2013, Michael Boelen # Copyright 2007-2021, CISOfy # # Website : https://cisofy.com # Blog : http://linux-audit.com # GitHub : https://github.com/CISOfy/lynis # # Lynis comes with ABSOLUTELY NO WARRANTY. This is free software, and you are # welcome to redistribute it under the terms of the GNU General Public License. # See LICENSE file for usage of this software. # ################################################################################# # # SSH # ################################################################################# # SSH_DAEMON_CONFIG_LOCS="/etc /etc/ssh /usr/local/etc/ssh /opt/csw/etc/ssh /usr/etc/ssh" SSH_DAEMON_CONFIG="" SSH_DAEMON_PORT="" SSH_DAEMON_RUNNING=0 SSH_DAEMON_OPTIONS_FILE="" OPENSSHD_RUNNING=0 OPENSSHD_VERSION=0 OPENSSHD_VERSION_MAJOR=0 OPENSSHD_VERSION_MINOR=0 # ################################################################################# # InsertSection "${SECTION_SSH_SUPPORT}" # ################################################################################# # # Test : SSH-7402 # Description : Check for a running SSH daemon Register --test-no SSH-7402 --weight L --network NO --category security --description "Check for running SSH daemon" if [ ${SKIPTEST} -eq 0 ]; then LogText "Test: Searching for a SSH daemon" if IsRunning "sshd"; then OPENSSHD_RUNNING=1 SSH_DAEMON_RUNNING=1 Display --indent 2 --text "- Checking running SSH daemon" --result "${STATUS_FOUND}" --color GREEN # Store settings in a temporary file CreateTempFile SSH_DAEMON_OPTIONS_FILE="${TEMP_FILE}" # Use a non-existing user, to ensure that systems that have a Match block configured, will be evaluated as well ${SSHDBINARY} -T -C user=doesnotexist,host=none,addr=none 2> /dev/null > ${SSH_DAEMON_OPTIONS_FILE} elif PortIsListening "TCP" 22; then Display --indent 2 --text "- Checking running SSH daemon" --result "${STATUS_FOUND}" --color GREEN SSH_DAEMON_RUNNING=1 else Display --indent 2 --text "- Checking running SSH daemon" --result "${STATUS_NOT_FOUND}" --color WHITE fi fi # ################################################################################# # # Test : SSH-7404 # Description : Determine SSH daemon configuration file location if [ ${OPENSSHD_RUNNING} -eq 1 ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi Register --test-no SSH-7404 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Check SSH daemon file location" if [ ${SKIPTEST} -eq 0 ]; then FOUND=0 LogText "Test: searching for sshd_config file" for I in ${SSH_DAEMON_CONFIG_LOCS}; do if [ -f "${I}/sshd_config" ]; then LogText "Result: ${I}/sshd_config exists" if [ ${FOUND} -eq 1 ]; then ReportException "${TEST_NO}:01" LogText "Result: we already found another sshd_config file. Using this new file instead of the previous one." fi FileIsReadable ${I}/sshd_config if [ ${CANREAD} -eq 1 ]; then FOUND=1 SSH_DAEMON_CONFIG="${I}/sshd_config" else LogText "Result: can not read ${I}/sshd_config file (no permission)" fi fi done if [ -z "${SSH_DAEMON_CONFIG}" ]; then LogText "Result: No sshd configuration found" Display --indent 4 --text "- Searching SSH configuration" --result "${STATUS_NOT_FOUND}" --color YELLOW ReportException "${TEST_NO}:1" "SSH daemon is running, but no readable configuration file found" else LogText "Result: using last found configuration file: ${SSH_DAEMON_CONFIG}" Display --indent 4 --text "- Searching SSH configuration" --result "${STATUS_FOUND}" --color GREEN fi fi # ################################################################################# # # Test : SSH-7406 # Description : Check OpenSSH version if [ ${OPENSSHD_RUNNING} -eq 1 ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi Register --test-no SSH-7406 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Determine OpenSSH version" if [ ${SKIPTEST} -eq 0 ]; then OPENSSHD_VERSION=$(${SSHDBINARY} -t -d 2>&1 | ${GREPBINARY} 'sshd version' | ${AWKBINARY} '{if($4~OpenSSH_){print $4}}' | ${AWKBINARY} -F_ '{print $2}' | ${TRBINARY} -d '[:cntrl:],') LogText "Result: discovered OpenSSH version is ${OPENSSHD_VERSION}" if [ -n "${OPENSSHD_VERSION}" ]; then OPENSSHD_VERSION_MAJOR=$(echo ${OPENSSHD_VERSION%%p*} | ${AWKBINARY} -F. '{print $1}') LogText "Result: OpenSSH major version: ${OPENSSHD_VERSION_MAJOR}" OPENSSHD_VERSION_MINOR=$(echo ${OPENSSHD_VERSION%%p*} | ${AWKBINARY} -F. '{print $2}') LogText "Result: OpenSSH minor version: ${OPENSSHD_VERSION_MINOR}" fi fi # ################################################################################# # # Test : SSH-7408 # Description : Check SSH specific defined options # Notes : Instead of parsing the configuration file, we query the SSH daemon itself if [ ${OPENSSHD_RUNNING} -eq 1 -a -n "${SSH_DAEMON_OPTIONS_FILE}" -a \( ${OPENSSHD_VERSION_MAJOR} -gt 5 -o ${OPENSSHD_VERSION_MAJOR} -eq 5 -a ${OPENSSHD_VERSION_MINOR} -ge 1 \) ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi Register --test-no SSH-7408 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Check SSH specific defined options" if [ ${SKIPTEST} -eq 0 ]; then LogText "Test: Checking specific defined options in ${SSH_DAEMON_OPTIONS_FILE}" ## SSHOPTIONS scheme: ## :,,: ## ## Test types: ## (a) '=' -- equal to is better, ## (b) '<' -- less or equal is better, ## (c) '>' -- more or equal is better, ## (d) '!' -- not equal is better. ## ## Example: ## PermitRootLogin:NO,WITHOUT-PASSWORD,YES,:= SSHOPS="AllowTcpForwarding:NO,LOCAL,YES:=\ ClientAliveCountMax:2,4,16:<\ ClientAliveInterval:300,600,900:<\ FingerprintHash:SHA256,MD5,:=\ GatewayPorts:NO,,YES:=\ IgnoreRhosts:YES,,NO:=\ LoginGraceTime:120,240,480:<\ LogLevel:VERBOSE,INFO,:=\ MaxAuthTries:3,6,999:<\ MaxSessions:2,4,8:<\ PermitRootLogin:(FORCED-COMMANDS-ONLY|NO|PROHIBIT-PASSWORD|WITHOUT-PASSWORD),,YES:=\ PermitUserEnvironment:NO,,YES:=\ PermitTunnel:NO,,YES:=\ Port:,,22:!\ PrintLastLog:YES,,NO:=\ StrictModes:YES,,NO:=\ TCPKeepAlive:NO,,YES:=\ UseDNS:NO,,YES:=\ X11Forwarding:NO,,YES:=\ AllowAgentForwarding:NO,,YES:=" # OpenSSH had some options removed over time. Based on the version we add some additional options to check if [ ${OPENSSHD_VERSION_MAJOR} -lt 7 ]; then LogText "Result: added additional options for OpenSSH 6.x and lower" SSHOPS="${SSHOPS} Compression:(DELAYED|NO),,YES:= UsePrivilegeSeparation:SANDBOX,YES,NO:= Protocol:2,,1:=" elif [ ${OPENSSHD_VERSION_MAJOR} -eq 7 ]; then # Protocol 1 support removed (OpenSSH 7.4 and later) if [ ${OPENSSHD_VERSION_MINOR} -lt 4 ]; then LogText "Result: added additional options for OpenSSH < 7.4" SSHOPS="${SSHOPS} Compression:(DELAYED|NO),,YES:= Protocol:2,,1:=" fi # UsePrivilegedSeparation removed (OpenSSH 7.5 and later) if [ ${OPENSSHD_VERSION_MINOR} -lt 5 ]; then LogText "Result: added additional options for OpenSSH < 7.5" SSHOPS="${SSHOPS} UsePrivilegeSeparation:SANDBOX,YES,NO:=" fi fi # Go through our list of options for I in ${SSHOPS}; do OPTIONNAME=$(echo ${I} | ${CUTBINARY} -d ':' -f1) OPTIONNAME_LOWER=$(echo ${I} | ${CUTBINARY} -d ':' -f1 | ${AWKBINARY} '{ print tolower($1) }') EXPECTEDVALUE=$(echo ${I} | ${CUTBINARY} -d ':' -f2 | ${CUTBINARY} -d',' -f1) MEDIUMSCOREDVALUE=$(echo ${I} | ${CUTBINARY} -d ':' -f2 | ${CUTBINARY} -d',' -f2) WEAKVALUE=$(echo ${I} | ${CUTBINARY} -d ':' -f2 | ${CUTBINARY} -d',' -f3) TESTTYPE=$(echo ${I} | ${CUTBINARY} -d ':' -f3) RESULT="NONE" if ! SkipAtomicTest "${TEST_NO}:${OPTIONNAME_LOWER}"; then # Get value and use the last occurrence FOUNDVALUE=$(${AWKBINARY} -v OPT="${OPTIONNAME_LOWER}" 'index($0, OPT) == 1 { print toupper($2) }' ${SSH_DAEMON_OPTIONS_FILE} | tail -1) LogText "Test: Checking ${OPTIONNAME} in ${SSH_DAEMON_OPTIONS_FILE}" if [ -n "${FOUNDVALUE}" ]; then LogText "Result: Option ${OPTIONNAME} found" LogText "Result: Option ${OPTIONNAME} value is ${FOUNDVALUE}" if [ "${TESTTYPE}" = "=" ]; then if [ "${FOUNDVALUE}" = "${EXPECTEDVALUE}" ]; then RESULT="GOOD" elif [ "${FOUNDVALUE}" = "${MEDIUMSCOREDVALUE}" ]; then RESULT="MIDSCORED" elif [ "${FOUNDVALUE}" = "${WEAKVALUE}" ]; then RESULT="WEAK" else if [ -n "${EXPECTEDVALUE}" ]; then LogText "Expected value has multiple values, testing if active value is in list (${EXPECTEDVALUE})" FIND=$(echo ${FOUNDVALUE} | ${GREPBINARY} -E "${EXPECTEDVALUE}") if [ $? -eq 0 ]; then LogText "Result: found" RESULT="GOOD" else LogText "Result: not found" fi fi if [ -n "${MEDIUMSCOREDVALUE}" ]; then LogText "Medium scored value has multiple values, testing if active value is in list (${MEDIUMSCOREDVALUE})" FIND=$(echo ${FOUNDVALUE} | ${GREPBINARY} -E "${MEDIUMSCOREDVALUE}") if [ $? -eq 0 ]; then LogText "Result: found" RESULT="MIDSCORED" else LogText "Result: not found" fi fi # Set result to weak if we can't find any matches if [ "${RESULT}" = "NONE" ]; then RESULT="WEAK"; fi fi elif [ "${TESTTYPE}" = "<" ]; then if [ "${FOUNDVALUE}" -ge "${WEAKVALUE}" -o "${FOUNDVALUE}" -gt "${MEDIUMSCOREDVALUE}" ]; then RESULT="WEAK" elif [ "${FOUNDVALUE}" -le "${MEDIUMSCOREDVALUE}" -a "${FOUNDVALUE}" -gt "${EXPECTEDVALUE}" ]; then RESULT="MIDSCORED" elif [ "${FOUNDVALUE}" -le "${EXPECTEDVALUE}" ]; then RESULT="GOOD" else RESULT="UNKNOWN" fi elif [ "${TESTTYPE}" = ">" ]; then if [ "${FOUNDVALUE}" -le "${WEAKVALUE}" ]; then RESULT="WEAK" elif [ "${FOUNDVALUE}" -le "${WEAKVALUE}" -a "${FOUNDVALUE}" -ge "${MEDIUMSCOREDVALUE}" ]; then RESULT="MIDSCORED" elif [ "${FOUNDVALUE}" -ge "${EXPECTEDVALUE}" ]; then RESULT="GOOD" else RESULT="UNKNOWN" fi elif [ "${TESTTYPE}" = "!" ]; then if [ "${FOUNDVALUE}" = "${WEAKVALUE}" ]; then RESULT="WEAK" elif [ ! "${FOUNDVALUE}" = "${WEAKVALUE}" ]; then RESULT="GOOD" else RESULT="UNKNOWN" fi else RESULT="NONE" fi fi if [ "${RESULT}" = "GOOD" ]; then LogText "Result: OpenSSH option ${OPTIONNAME} is configured very well" Display --indent 4 --text "- OpenSSH option: ${OPTIONNAME}" --result "${STATUS_OK}" --color GREEN AddHP 3 3 elif [ "${RESULT}" = "MIDSCORED" ]; then LogText "Result: OpenSSH option ${OPTIONNAME} is configured reasonably" ReportSuggestion "${TEST_NO}" "Consider hardening SSH configuration" "${OPTIONNAME} (set ${FOUNDVALUE} to ${EXPECTEDVALUE})" "-" ReportDetails --test "${TEST_NO}" --service "sshd" --field "${OPTIONNAME}" --value "${FOUNDVALUE}" --preferredvalue "${EXPECTEDVALUE}" --description "sshd option ${OPTIONNAME}" Display --indent 4 --text "- OpenSSH option: ${OPTIONNAME}" --result "${STATUS_SUGGESTION}" --color YELLOW AddHP 1 3 elif [ "${RESULT}" = "WEAK" ]; then LogText "Result: OpenSSH option ${OPTIONNAME} is in a weak configuration state and should be fixed" ReportSuggestion "${TEST_NO}" "Consider hardening SSH configuration" "${OPTIONNAME} (set ${FOUNDVALUE} to ${EXPECTEDVALUE})" "-" ReportDetails --test "${TEST_NO}" --service "sshd" --field "${OPTIONNAME}" --value "${FOUNDVALUE}" --preferredvalue "${EXPECTEDVALUE}" --description "sshd option ${OPTIONNAME}" Display --indent 4 --text "- OpenSSH option: ${OPTIONNAME}" --result "${STATUS_SUGGESTION}" --color YELLOW AddHP 0 3 elif [ "${RESULT}" = "UNKNOWN" ]; then LogText "Result: Value of OpenSSH option ${OPTIONNAME} is unknown (not defined)" Display --indent 4 --text "- OpenSSH option: ${OPTIONNAME}" --result DEFAULT --color WHITE Report "unknown_config_option[]=ssh|$SSH_DAEMON_CONFIG}|${OPTIONNAME}|" else LogText "Result: Option ${OPTIONNAME} not found in output" Display --indent 4 --text "- OpenSSH option: ${OPTIONNAME}" --result "${STATUS_NOT_FOUND}" --color WHITE fi else if IsVerbose; then Display --indent 4 --text "- OpenSSH option: ${OPTIONNAME}" --result "SKIPPED (via config)" --color WHITE; fi fi done fi # ################################################################################# # # Test : SSH-7440 # Description : OpenSSH - AllowUsers / AllowGroups # Goal : Check if only a specific amount of users/groups can log in to the system if [ ${OPENSSHD_RUNNING} -eq 1 -a -n "${SSH_DAEMON_OPTIONS_FILE}" ]; then PREQS_MET="YES"; else PREQS_MET="NO"; fi Register --test-no SSH-7440 --preqs-met ${PREQS_MET} --weight L --network NO --category security --description "Check OpenSSH option: AllowUsers and AllowGroups" if [ ${SKIPTEST} -eq 0 ]; then FOUND=0 # AllowUsers FIND=$(${GREPBINARY} -E -i "^AllowUsers" ${SSH_DAEMON_OPTIONS_FILE} | ${AWKBINARY} '{ print $2 }') if [ -n "${FIND}" ]; then LogText "Result: AllowUsers set, with value ${FIND}" Display --indent 4 --text "- OpenSSH option: AllowUsers" --result "${STATUS_FOUND}" --color GREEN FOUND=1 else LogText "Result: AllowUsers is not set" Display --indent 4 --text "- OpenSSH option: AllowUsers" --result "${STATUS_NOT_FOUND}" --color WHITE fi # AllowGroups FIND=$(${GREPBINARY} -E -i "^AllowGroups" ${SSH_DAEMON_OPTIONS_FILE} | ${AWKBINARY} '{ print $2 }') if [ -n "${FIND}" ]; then LogText "Result: AllowGroups set ${FIND}" Display --indent 4 --text "- OpenSSH option: AllowGroups" --result "${STATUS_FOUND}" --color GREEN FOUND=1 else LogText "Result: AllowGroups is not set" Display --indent 4 --text "- OpenSSH option: AllowGroups" --result "${STATUS_NOT_FOUND}" --color WHITE fi if [ ${FOUND} -eq 1 ]; then LogText "Result: SSH is limited to a specific set of users, which is good" AddHP 2 2 else LogText "Result: SSH has no specific user or group limitation. Most likely all valid users can SSH to this machine." AddHP 0 1 fi fi # ################################################################################# # Report "ssh_daemon_running=${SSH_DAEMON_RUNNING}" Report "openssh_daemon_running=${OPENSSHD_RUNNING}" WaitForKeyPress # #================================================================================ # Lynis - Security Auditing and System Hardening for Linux and UNIX - https://cisofy.com