#!/bin/bash
#< Script to check Red Hat Enterprise Linux AS 4 OS-level security for security risks
# Designed to be run whilst redirecting output to two files, thus:
# /path/to/chkhard.sh >/root/chkhard/listings.out 2>/root/chkhard/security_risks.out
# i.e. - file output is dumped to listings.out for analysis, whilst all checked
# security risks are output to security_risks.out.
# This script sticks a few "best" security principles from various sources (CISecurity.org,
# and many others), customised for my environment. The script is VERY rigid. But you can
# just ignore problems that you're not concerned with
# ToDo: Make most settings user configurable
# Create variables to hold paths to executables
# Function: cleanup()
# Arguments: None
# Returns: Nothing
# Description: Removes all /tmp/chkhard* files, if any exist
cleanup() {
rm -rf /tmp/chkhard*
}
# Trap signals, cleanup and exit
trap 'cleanup(); exit 1' 1 2 3 15
# Function: print_error()
# Arguments: $@
# Returns: Nothing
# Description: Prints arguments to STDERR with prefix "ERROR: "
print_error() {
echo "ERROR: $@" >&2
}
# Function: print_warning()
# Arguments: $@
# Returns: Nothing
# Description: Prints arguments to STDERR with prefix "WARNING: "
print_warning() {
echo "WARNING: $@" >&2
}
# Function: check_os()
# Arguments: None
# Returns: Exits on failure
# Description: Ensures that the script is only executed on Red Hat Linux
check_os() {
[[ `uname -s` == "Linux" ]] && {
[[ -e /etc/redhat-release ]] || {
print_error "Sorry, only Red Hat Linux supported"
exit 1
}
}
}
# Function: check_root()
# Arguments: None
# Returns: Exits on failure
# Description: Ensures that the script is only run as root
check_root() {
[[ `id -u` -ne "0" ]] && {
print_error "This script must be run as \"root\""
exit 1
}
}
# Function: check_fstab()
# Arguments: None
# Returns: Nothing
# Description: Goes through /etc/fstab and checks that various mount options are set
# on various filesystems. These are currently hard coded.
# ToDo: Make filesystems and mount options to check configurable
check_fstab() {
[[ ! -e "/etc/fstab" ]] && {
print_warning "/etc/fstab does not exist"
} || {
# First, check /boot filesystem
egrep '^[^ ]+[ ]+/boot[ ]+.*$' /etc/fstab >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
BOOT_FS_LINE=`egrep '^[^ ]+[ ]+/boot[ ]+.*$' /etc/fstab`
BOOT_FS_MOUNT_OPTIONS=`echo "${BOOT_FS_LINE}" | awk '{print $4}'`
echo "${BOOT_FS_MOUNT_OPTIONS}" | awk -vRS=',' '1' | grep -v '^[ ]*$' > /tmp/chkhard_boot_fs_options_$$
BOOT_FS_REQUIRED_OPTIONS="defaults\nacl"
echo -e "${BOOT_FS_REQUIRED_OPTIONS}" | while read OPTION; do
grep "^${OPTION}$" /tmp/chkhard_boot_fs_options_$$ >/dev/null 2>&1 || {
print_warning "${OPTION} mount option not set for /boot filesystem"
}
done
} || {
print_warning "Could not find /boot filesystem in /etc/fstab"
}
# Next, root filesystem
egrep '^[^ ]+[ ]+/[ ]+.*$' /etc/fstab >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
ROOT_FS_LINE=`egrep '^[^ ]+[ ]+/[ ]+.*$' /etc/fstab`
ROOT_FS_MOUNT_OPTIONS=`echo "${ROOT_FS_LINE}" | awk '{print $4}'`
echo "${ROOT_FS_MOUNT_OPTIONS}" | awk -vRS=',' '1' | grep -v '^[ ]*$' > /tmp/chkhard_root_fs_options_$$
ROOT_FS_REQUIRED_OPTIONS="defaults"
echo -e "${ROOT_FS_REQUIRED_OPTIONS}" | while read OPTION; do
grep "^${OPTION}$" /tmp/chkhard_root_fs_options_$$ >/dev/null 2>&1 || {
print_warning "${OPTION} mount option not set for / filesystem"
}
done
} || {
print_warning "Could not find / filesystem in /etc/fstab"
}
# Next, /var filesystem
egrep '^[^ ]+[ ]+/var[ ]+.*$' /etc/fstab >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
VAR_FS_LINE=`egrep '^[^ ]+[ ]+/var[ ]+.*$' /etc/fstab`
VAR_FS_MOUNT_OPTIONS=`echo "${VAR_FS_LINE}" | awk '{print $4}'`
echo "${VAR_FS_MOUNT_OPTIONS}" | awk -vRS=',' '1' | grep -v '^[ ]*$' > /tmp/chkhard_var_fs_options_$$
VAR_FS_REQUIRED_OPTIONS="defaults\nnosuid\nacl\nnodev"
echo -e "${VAR_FS_REQUIRED_OPTIONS}" | while read OPTION; do
grep "^${OPTION}$" /tmp/chkhard_var_fs_options_$$ >/dev/null 2>&1 || {
print_warning "${OPTION} mount option not set for /var filesystem"
}
done
} || {
print_warning "Could not find /var filesystem in /etc/fstab"
}
# Next, /var/log filesystem
egrep '^[^ ]+[ ]+/var/log[ ]+.*$' /etc/fstab >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
VAR_LOG_FS_LINE=`egrep '^[^ ]+[ ]+/var/log[ ]+.*$' /etc/fstab`
VAR_LOG_FS_MOUNT_OPTIONS=`echo "${VAR_LOG_FS_LINE}" | awk '{print $4}'`
echo "${VAR_LOG_FS_MOUNT_OPTIONS}" | awk -vRS=',' '1' | grep -v '^[ ]*$' > /tmp/chkhard_var_log_fs_options_$$
VAR_LOG_FS_REQUIRED_OPTIONS="defaults\nnosuid\nacl\nnodev"
echo -e "${VAR_LOG_FS_REQUIRED_OPTIONS}" | while read OPTION; do
grep "^${OPTION}$" /tmp/chkhard_var_log_fs_options_$$ >/dev/null 2>&1 || {
print_warning "${OPTION} mount option not set for /var/log filesystem"
}
done
} || {
print_warning "Could not find /var/log filesystem in /etc/fstab"
}
# Next, /tmp filesystem
egrep '^[^ ]+[ ]+/tmp[ ]+.*$' /etc/fstab >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
TMP_FS_LINE=`egrep '^[^ ]+[ ]+/tmp[ ]+.*$' /etc/fstab`
TMP_FS_MOUNT_OPTIONS=`echo "${TMP_FS_LINE}" | awk '{print $4}'`
echo "${TMP_FS_MOUNT_OPTIONS}" | awk -vRS=',' '1' | grep -v '^[ ]*$' > /tmp/chkhard_tmp_fs_options_$$
TMP_FS_REQUIRED_OPTIONS="defaults\nnosuid\nacl\nnodev"
echo -e "${TMP_FS_REQUIRED_OPTIONS}" | while read OPTION; do
grep "^${OPTION}$" /tmp/chkhard_tmp_fs_options_$$ >/dev/null 2>&1 || {
print_warning "${OPTION} mount option not set for /tmp filesystem"
}
done
} || {
print_warning "Could not find /tmp filesystem in /etc/fstab"
}
# Finally, /home
egrep '^[^ ]+[ ]+/home[ ]+.*$' /etc/fstab >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
HOME_FS_LINE=`egrep '^[^ ]+[ ]+/tmp[ ]+.*$' /etc/fstab`
HOME_FS_MOUNT_OPTIONS=`echo "${HOME_FS_LINE}" | awk '{print $4}'`
echo "${HOME_FS_MOUNT_OPTIONS}" | awk -vRS=',' '1' | grep -v '^[ ]*$' > /tmp/chkhard_home_fs_options_$$
HOME_FS_REQUIRED_OPTIONS="defaults\nnosuid\nacl\nnodev"
echo -e "${HOME_FS_REQUIRED_OPTIONS}" | while read OPTION; do
grep "^${OPTION}$" /tmp/chkhard_home_fs_options_$$ >/dev/null 2>&1 || {
print_warning "${OPTION} mount option not set for /home filesystem"
}
done
} || {
print_warning "Could not find /home filesystem in /etc/fstab"
}
# And simply check for the existance of a swap partition
egrep '^[^ ]+[ ]+swap[ ]+.*$' /etc/fstab >/dev/null 2>&1
[[ "$?" -eq "0" ]] || {
print_warning "Could not find swap partition in /etc/fstab"
}
}
}
# Function: check_required_software()
# Arguments: None
# Returns: Nothing
# Description: Check that various software packages are installed
check_required_software() {
for RPM in sudo tcp_wrappers openssh sysstat; do
rpm -qi ${RPM} >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "Package \"${RPM}\" not installed"
}
done
}
# Function: check_banned_software()
# Arguments: None
# Returns: Nothing
# Description: Check that various software packages are not installed
check_banned_software() {
for RPM in tcpdump nmap netcat hotplug kernel-pcmcia-cs pcmcia-cs; do
rpm -qi ${RPM} >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
print_warning "Package \"${RPM}\" is installed"
}
done
}
# Function: display_messages()
# Arguments: None
# Returns: Nothing
# Description: Dump contents of /etc/{motd,issue,issue.net} to STDOUT
display_messages() {
echo "======================================================================="
echo "Check the output of the following commands for message file compliance:"
echo "======================================================================="
[[ -e "/etc/motd" ]] && {
echo "$ cat /etc/motd"
cat /etc/motd
} || {
print_warning "/etc/motd does not exist"
}
[[ -e "/etc/issue" ]] && {
echo "$ cat /etc/issue"
cat /etc/issue
} || {
print_warning "/etc/issue does not exist"
}
[[ -e "/etc/issue.net" ]] && {
echo "$ cat /etc/issue.net"
cat /etc/issue.net
} || {
print_warning "/etc/issue.net does not exist"
}
}
# Function: display_services()
# Arguments: None
# Returns: Nothing
# Description: Show listening network services, and services enabled via chkconfig
display_services() {
echo "======================================================================="
echo "Check the output of the following commands to ensure appropriate"
echo "service lockdown has been applied:"
echo "======================================================================="
echo "$ netstat -anlp | grep LISTEN"
netstat -anlp | grep LISTEN
echo "$ chkconfig --list | grep on"
chkconfig --list | grep on
}
# Function: check_system_accounts()
# Arguments: None
# Returns: Nothing
# Description: Check for accounts that should be removed or locked
check_system_accounts() {
ACCOUNTS_TO_REMOVE="smtp\nlisten\nuucp\nnuucp\ngames\ngopher\nftp\nnetdump\nnfsnobody\npcap"
echo -e "${ACCOUNTS_TO_REMOVE}" | while read ACCOUNT; do
grep "^${ACCOUNT}:.*$" /etc/passwd >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
print_warning "System account ${ACCOUNT} exists and should be removed"
}
done
ACCOUNTS_TO_DISABLE="nobody4\ndaemon\nbin\nadm\nlp\nnobody\nnoaccess\nsmmsp\nsync\nshutdown"
ACCOUNTS_TO_DISABLE="${ACCOUNTS_TO_DISABLE}\nhalt\nmail\nnews\noperator\ndbus\nvcsa\nnscd"
ACCOUNTS_TO_DISABLE="${ACCOUNTS_TO_DISABLE}\nrpm\nhaldaemon\nsshd\nrpc\nrpcuser\nmailnull"
echo -e "${ACCOUNTS_TO_DISABLE}" | while read ACCOUNT; do
grep "^${ACCOUNT}:.*$" /etc/passwd >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
egrep "^${ACCOUNT}:(!!|\*):.*$" /etc/shadow >/dev/null 2>&1
[[ "$?" -eq "0" ]] || {
print_warning "System account ${ACCOUNT} exists and is NOT locked"
}
}
done
}
# Function: check_syslog_conf()
# Arguments: None
# Returns: Nothing
# Description: Display syslog.conf file on STDOUT for checking
check_syslog_conf() {
echo "======================================================================="
echo "Check the output of the following commands to ensure that the syslog"
echo "configuration has been set correctly"
echo "======================================================================="
[[ ! -e "/etc/syslog.conf" ]] && {
print_warning "/etc/syslog.conf does not exist. Maybe check your syslog-ng config?"
} || {
echo "$ cat /etc/syslog.conf"
cat /etc/syslog.conf
}
}
# Function: check_sysctl_conf()
# Arguments: None
# Returns: Nothing
# Description: Check that various parameters are set correctly in /etc/sysctl.conf
check_sysctl_conf() {
grep '^net\.ipv4\.tcp_max_syn_backlog[ ]*=.*$' /etc/sysctl.conf >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
NUM=`sed -n 's/^net\.ipv4\.tcp_max_syn_backlog[ ]*\=[ ]*\(.*\)$/\1/p' /etc/sysctl.conf`
[[ "${NUM}" -ne "4096" ]] && {
print_warning "net.ipv4.tcp_max_syn_backlog NOT set to 4096 in /etc/sysctl.conf (value: ${NUM})"
} || { :; }
} || {
print_warning "net.ipv4.tcp_max_syn_backlog NOT defined in /etc/sysctl.conf"
}
grep '^net\.ipv4\.tcp_syncookies[ ]*=.*$' /etc/sysctl.conf >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
NUM=`sed -n 's/^net\.ipv4\.tcp_syncookies[ ]*\=[ ]*\(.*\)$/\1/p' /etc/sysctl.conf`
[[ "${NUM}" -ne "1" ]] && {
print_warning "net.ipv4.tcp_max_syncookies NOT set to 1 in /etc/sysctl.conf (value: ${NUM})"
} || { :; }
} || {
print_warning "net.ipv4.tcp_max_syncookies NOT defined in /etc/sysctl.conf"
}
grep '^net\.ipv4\.conf\.all\.rp_filter[ ]*=.*$' /etc/sysctl.conf >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
NUM=`sed -n 's/^net\.ipv4\.conf\.all\.rp_filter[ ]*\=[ ]*\(.*\)$/\1/p' /etc/sysctl.conf`
[[ "${NUM}" -ne "1" ]] && {
print_warning "net.ipv4.conf.all.rp_filter NOT set to 1 in /etc/sysctl.conf (value: ${NUM})"
} || { :; }
} || {
print_warning "net.ipv4.conf.all.rp_filter NOT defined in /etc/sysctl.conf"
}
grep '^net\.ipv4\.conf\.all\.accept_source_route[ ]*=.*$' /etc/sysctl.conf >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
NUM=`sed -n 's/^net\.ipv4\.conf\.all\.accept_source_route[ ]*\=[ ]*\(.*\)$/\1/p' /etc/sysctl.conf`
[[ "${NUM}" -ne "0" ]] && {
print_warning "net.ipv4.conf.all.accept_source_route NOT set to 0 in /etc/sysctl.conf (value: ${NUM})"
} || { :; }
} || {
print_warning "net.ipv4.conf.all.accept_source_route NOT defined in /etc/sysctl.conf"
}
grep '^net\.ipv4\.conf\.all\.accept_redirects[ ]*=.*$' /etc/sysctl.conf >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
NUM=`sed -n 's/^net\.ipv4\.conf\.all\.accept_redirects[ ]*\=[ ]*\(.*\)$/\1/p' /etc/sysctl.conf`
[[ "${NUM}" -ne "0" ]] && {
print_warning "net.ipv4.conf.all.accept_redirects NOT set to 0 in /etc/sysctl.conf (value: ${NUM})"
} || { :; }
} || {
print_warning "net.ipv4.conf.all.accept_redirects NOT defined in /etc/sysctl.conf"
}
grep '^net\.ipv4\.conf\.all\.secure_redirects[ ]*=.*$' /etc/sysctl.conf >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
NUM=`sed -n 's/^net\.ipv4\.conf\.all\.secure_redirects[ ]*\=[ ]*\(.*\)$/\1/p' /etc/sysctl.conf`
[[ "${NUM}" -ne "0" ]] && {
print_warning "net.ipv4.conf.all.secure_redirects NOT set to 0 in /etc/sysctl.conf (value: ${NUM})"
} || { :; }
} || {
print_warning "net.ipv4.conf.all.secure_redirects NOT defined in /etc/sysctl.conf"
}
grep '^net\.ipv4\.conf\.default\.rp_filter[ ]*=.*$' /etc/sysctl.conf >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
NUM=`sed -n 's/^net\.ipv4\.conf\.default\.rp_filter[ ]*\=[ ]*\(.*\)$/\1/p' /etc/sysctl.conf`
[[ "${NUM}" -ne "1" ]] && {
print_warning "net.ipv4.conf.default.rp_filter NOT set to 1 in /etc/sysctl.conf (value: ${NUM})"
} || { :; }
} || {
print_warning "net.ipv4.conf.default.rp_filter NOT defined in /etc/sysctl.conf"
}
grep '^net\.ipv4\.conf\.default\.accept_redirects[ ]*=.*$' /etc/sysctl.conf >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
NUM=`sed -n 's/^net\.ipv4\.conf\.default\.accept_redirects[ ]*\=[ ]*\(.*\)$/\1/p' /etc/sysctl.conf`
[[ "${NUM}" -ne "0" ]] && {
print_warning "net.ipv4.conf.default.accept_redirects NOT set to 0 in /etc/sysctl.conf (value: ${NUM})"
} || { :; }
} || {
print_warning "net.ipv4.conf.default.accept_redirects NOT defined in /etc/sysctl.conf"
}
grep '^net\.ipv4\.conf\.default\.secure_redirects[ ]*=.*$' /etc/sysctl.conf >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
NUM=`sed -n 's/^net\.ipv4\.conf\.default\.secure_redirects[ ]*\=[ ]*\(.*\)$/\1/p' /etc/sysctl.conf`
[[ "${NUM}" -ne "0" ]] && {
print_warning "net.ipv4.conf.default.secure_redirects NOT set to 0 in /etc/sysctl.conf (value: ${NUM})"
} || { :; }
} || {
print_warning "net.ipv4.conf.default.secure_redirects NOT defined in /etc/sysctl.conf"
}
grep '^net\.ipv4\.icmp_echo_ignore_broadcasts[ ]*=.*$' /etc/sysctl.conf >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
NUM=`sed -n 's/^net\.ipv4\.icmp_echo_ignore_broadcasts[ ]*\=[ ]*\(.*\)$/\1/p' /etc/sysctl.conf`
[[ "${NUM}" -ne "1" ]] && {
print_warning "net.ipv4.icmp_echo_ignore_broadcasts NOT set to 1 in /etc/sysctl.conf (value: ${NUM})"
} || { :; }
} || {
print_warning "net.ipv4.icmp_echo_ignore_broadcasts NOT defined in /etc/sysctl.conf"
}
grep '^net\.ipv4\.ip_forward[ ]*=.*$' /etc/sysctl.conf >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
NUM=`sed -n 's/^net\.ipv4\.ip_forward[ ]*\=[ ]*\(.*\)$/\1/p' /etc/sysctl.conf`
[[ "${NUM}" -ne "0" ]] && {
print_warning "net.ipv4.ip_forward NOT set to 0 in /etc/sysctl.conf (value: ${NUM})"
} || { :; }
} || {
print_warning "net.ipv4.ip_forward NOT defined in /etc/sysctl.conf"
}
grep '^net\.ipv4\.conf\.all\.send_redirects[ ]*=.*$' /etc/sysctl.conf >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
NUM=`sed -n 's/^net\.ipv4\.conf\.all\.send_redirects[ ]*\=[ ]*\(.*\)$/\1/p' /etc/sysctl.conf`
[[ "${NUM}" -ne "0" ]] && {
print_warning "net.ipv4.conf.all.send_redirects NOT set to 0 in /etc/sysctl.conf (value: ${NUM})"
} || { :; }
} || {
print_warning "net.ipv4.conf.all.send_redirects NOT defined in /etc/sysctl.conf"
}
grep '^net\.ipv4\.conf\.default\.send_redirects[ ]*=.*$' /etc/sysctl.conf >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
NUM=`sed -n 's/^net\.ipv4\.conf\.default\.send_redirects[ ]*\=[ ]*\(.*\)$/\1/p' /etc/sysctl.conf`
[[ "${NUM}" -ne "0" ]] && {
print_warning "net.ipv4.conf.default.send_redirects NOT set to 0 in /etc/sysctl.conf (value: ${NUM})"
} || { :; }
} || {
print_warning "net.ipv4.conf.default.send_redirects NOT defined in /etc/sysctl.conf"
}
}
# Function: display_host_access()
# Arguments: None
# Returns: Nothing
# Description: Display contents of hosts.{allow,deny} for checking
display_host_access() {
echo "======================================================================="
echo "Check the output of the following commands to ensure that the TCP"
echo "wrappers configuration has been set correctly"
echo "======================================================================="
[[ ! -e "/etc/hosts.allow" ]] && {
print_warning "/etc/hosts.allow does not exist"
} || {
echo "$ cat /etc/hosts.allow"
cat /etc/hosts.allow
}
[[ ! -e "/etc/hosts.deny" ]] && {
print_warning "/etc/hosts.deny does not exist"
} || {
echo "$ cat /etc/hosts.deny"
cat /etc/hosts.deny
}
}
# Function: check_sendmail()
# Arguments: None
# Returns: Nothing
# Description: Check some basic sendmail configuration parameters
check_sendmail() {
# If either of these files do not exist, don't worry about it
[[ -e "/etc/mail/sendmail.cf" ]] && {
grep '^O SmtpGreetingMessage.*$' /etc/mail/sendmail.cf >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
SETTING=`sed -n 's/^O SmtpGreetingMessage[ ]*=[ ]*\(.*\)$/\1/p' /etc/mail/sendmail.cf`
echo "${SETTING}" | grep "^mailer ready$" >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "O SmtpGreetingMessage not set to \"mailer ready\" in /etc/mail/sendmail.cf"
} || { :; }
} || {
print_warning "O SmtpGreetingMessage not defined in /etc/mail/sendmail.cf"
}
}
[[ -e "/etc/sysconfig/sendmail" ]] && {
grep '^DAEMON.*$' /etc/sysconfig/sendmail >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
SETTING=`sed -n 's/^DAEMON[ ]*=[ ]*\(.*\)$/\1/p' /etc/sysconfig/sendmail`
echo "${SETTING}" | grep "^no$" >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "DAEMON not set to \"no\" in /etc/sysconfig/sendmail"
} || { :; }
} || {
print_warning "DAEMON not defined in /etc/sysconfig/sendmail"
}
grep '^QUEUE.*$' /etc/sysconfig/sendmail >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
SETTING=`sed -n 's/^QUEUE[ ]*=[ ]*\(.*\)$/\1/p' /etc/sysconfig/sendmail`
echo "${SETTING}" | grep "^15m$" >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "QUEUE not set to \"15m\" in /etc/sysconfig/sendmail"
} || { :; }
} || {
print_warning "QUEUE not defined in /etc/sysconfig/sendmail"
}
}
}
# Function: check_sshd_config()
# Arguments: None
# Returns: Nothing
# Description: Check that lockdown has been applied to /etc/ssh/sshd_config
check_sshd_config() {
[[ -e "/etc/ssh/sshd_config" ]] && {
# get IP of primary interface, we'll assume bond0
INTERFACE="bond0"
INET_ADDR=`ifconfig ${INTERFACE} | sed -n '/^[ ]*inet addr.*$/ s/^.*inet addr:\([^ ]*\).*$/\1/p'`
PORT="22"
grep "^ListenAddress[ ]*${INET_ADDR}$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "ListenAddress not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^Port[ ]*${PORT}$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "Port not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^Protocol[ ]*2$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "Protocol not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^Banner[ ]*/etc/issue\.net$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "Banner not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^UsePrivilegeSeparation[ ]*yes$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "UsePrivilegeSeparation not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^ChallengeResponseAuthentication[ ]*no$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "ChallengeResponseAuthentication not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^PAMAuthenticationViaKbdInt[ ]*no$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "PAMAuthenticationViaKbdInt not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^X11Forwarding[ ]*no$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "X11Forwarding not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^AllowTcpForwarding[ ]*no$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "AllowTcpForwarding not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^Ciphers[ ]*aes128-cbc,3des-cbc,aes192-cbc,aes256-cbc$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "Ciphers not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^ClientAliveInterval[ ]*10$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "ClientAliveInterval not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^ClientAliveCountMax[ ]*5$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "ClientAliveCountMax not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^HostbasedAuthentication[ ]*no$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "HostbasedAuthentication not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^IgnoreRhosts[ ]*no$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "IgnoreRhosts not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^KeepAlive[ ]*yes$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "KeepAlive not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^PermitEmptyPasswords[ ]*no$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "PermitEmptyPasswords not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^PermitRootLogin[ ]*no$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "PermitRootLogin not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^PermitUserEnvironment[ ]*no$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "PermitUserEnvironment not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^RhostsAuthentication[ ]*no$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "RhostsAuthentication not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^RhostsRSAAuthentication[ ]*no$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "RhostsRSAAuthentication not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^StrictModes[ ]*yes$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "StrictModes not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^UseLogin[ ]*no$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "UseLogin not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^VerifyReverseMapping[ ]*yes$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "VerifyReverseMapping not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^KeyRegenerationInterval[ ]*30m$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "KeyRegenerationInterval not correctly defined in /etc/ssh/sshd_config"
} || :
grep "^ServerKeyBits[ ]*1024$" /etc/ssh/sshd_config >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "ServerKeyBits not correctly defined in /etc/ssh/sshd_config"
} || :
} || {
print_warning "/etc/ssh/sshd_config does not exist"
}
}
# Function: check_su()
# Arguments: None
# Returns: Nothing
# Description: Ensure that su has correct ownership and permissions
check_su() {
SU_PATHS="/bin/su\n/usr/bin/su\n/sbin/su.static"
echo -e "${SU_PATHS}" | while read SU_PATH; do
[[ -e "${SU_PATH}" ]] && {
LONG_LISTING=`ls -ld "${SU_PATH}"`
SU_PERM=`echo "${LONG_LISTING}" | awk '{print $1}'`
SU_OWNER=`echo "${LONG_LISTING}" | awk '{print $3}'`
SU_GROUP=`echo "${LONG_LISTING}" | awk '{print $3}'`
[[ "${SU_PERM}" != "-rws--x---" ]] && {
print_warning "${SU_PATH} permission incorrect"
} || :
[[ "${SU_OWNER}" != "root" ]] && {
print_warning "${SU_PATH} ownership incorrect"
} || :
[[ "${SU_GROUP}" != "wheel" ]] && {
print_warning "${SU_PATH} group ownership incorrect"
} || :
}
done
grep '^auth.*required.*pam_wheel.so.*use_uid' /etc/pam.d/su >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warn "/etc/pam.d/su not correctly configured"
}
}
# Function: check_symlinks()
# Arguments: None
# Returns: Nothing
# Description: Ensure that hosts.equiv is linked to /dev/null
check_symlinks() {
[[ -e "/etc/hosts.equiv" ]] && {
LONG_LISTING=`ls -ld /etc/hosts.equiv`
LAST_FIELD=`echo "${LONG_LISTING}" | awk '{print $NF}'`
[[ "${LAST_FIELD}" != "/dev/null" ]] && {
print_warning "/etc/hosts.equiv not linked to /dev/null"
} || :
} || {
print_warning "/etc/hosts.equiv does not exist"
}
[[ -e "/etc/shosts.equiv" ]] && {
LONG_LISTING=`ls -ld /etc/shosts.equiv`
LAST_FIELD=`echo "${LONG_LISTING}" | awk '{print $NF}'`
[[ "${LAST_FIELD}" != "/dev/null" ]] && {
print_warning "/etc/shosts.equiv not linked to /dev/null"
} || :
} || {
print_warning "/etc/shosts.equiv does not exist"
}
}
# Function: check_stack_protection()
# Arguments: None
# Returns: Nothing
# Description: Ensure that stack protection is enabled in /etc/sysctl.conf
check_stack_protection() {
grep '^kernel\.exec-shield[ ]*=.*$' /etc/sysctl.conf >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
NUM=`sed -n 's/^kernel\.exec-shield[ ]*\=[ ]*\(.*\)$/\1/p' /etc/sysctl.conf`
[[ "${NUM}" -ne "1" ]] && {
print_warning "kernel.exec-shield NOT set to 0 in /etc/sysctl.conf (value: ${NUM})"
} || { :; }
} || {
print_warning "kernel.exec-shield NOT defined in /etc/sysctl.conf"
}
grep '^kernel\.exec-shield-randomize[ ]*=.*$' /etc/sysctl.conf >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
NUM=`sed -n 's/^kernel\.exec-shield-randomize[ ]*\=[ ]*\(.*\)$/\1/p' /etc/sysctl.conf`
[[ "${NUM}" -ne "1" ]] && {
print_warning "kernel.exec-shield-randomize NOT set to 0 in /etc/sysctl.conf (value: ${NUM})"
} || { :; }
} || {
print_warning "kernel.exec-shield-randomize NOT defined in /etc/sysctl.conf"
}
}
# Function: check_umask()
# Arguments: None
# Returns: Nothing
# Description: Check that umaks is adequately restrictive
check_umask() {
grep '^[ ]*umask 027$' /etc/init.d/functions >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "umask not correctly set in /etc/init.d/functions"
}
for INITFILE in /etc/profile /etc/.login/etc/profile /etc/csh.login /etc/csh.cshrc /etc/bashrc; do
[[ -e "${INITFILE}" ]] && {
grep "^[ ]*umask 077$" ${INITFILE} >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "umask not correctly set in ${INITFILE}"
} || { :; }
}
done
}
# Function: check_mesg()
# Arguments: None
# Returns: Nothing
# Description: mesg n!
check_mesg() {
for INITFILE in /etc/profile /etc/.login/etc/profile /etc/csh.login /etc/csh.cshrc /etc/bashrc; do
[[ -e "${INITFILE}" ]] && {
grep "^[ ]*mesg n$" ${INITFILE} >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "mesg not correctly set in ${INITFILE}"
} || { :; }
}
done
}
# Function: check_system_auth()
# Arguments: None
# Returns: Nothing
# Description: Check that pam_tally is configured to lock out accounts after 3 incorrect
# password attempts. Of course, ignore root!
check_system_auth() {
grep '^auth.*required.*pam_tally.so.*onerr=fail.*no_magic_root$' /etc/pam.d/system-auth >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "auth required line for pam_tally not correctly defined in /etc/pam.d/system-auth"
}
grep '^account.*required.*pam_tally.so.*deny=3.*reset.*no_magic_root$' /etc/pam.d/system-auth >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "account required line for pam_tally not correctly defined in /etc/pam.d/system-auth"
}
}
# Function: check_securetty()
# Arguments: None
# Returns: Nothing
# Description: Ensure that root can only log in on specified consoles/terminals
check_securetty() {
cat <<EoF >/tmp/chkhard_securetty.$$
console
vc/1
vc/2
vc/3
vc/4
vc/5
vc/6
vc/7
vc/8
vc/9
vc/10
vc/11
tty1
tty2
tty3
tty4
tty5
tty6
tty7
tty8
tty9
tty10
tty11
EoF
[[ -e "/etc/securetty" ]] && {
diff /etc/securetty /tmp/chkhard_securetty.$$ >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "/etc/securetty not correctly populated"
} || :
} || {
print_warning "/etc/securetty does not exist"
}
}
# Function: check_cron_access()
# Arguments: None
# Returns: Nothing
# Description: Check for cron/at restrictions. This function assumes a lot, i.e. that you
# only want "root" to access cron and at. However, you can just check the output
# and ignore the warnings if other users require access to these facilities
check_cron_access() {
[[ -e "/etc/at.allow" ]] && {
NUM_LINES=`grep -v 'root' /etc/at.allow | wc -l`
[[ "${NUM_LINES}" -ne "0" ]] && {
print_warning "users other than root exist in /etc/at.allow"
} || :
} || {
print_warning "/etc/at.allow does not exist"
}
[[ -e "/etc/cron.allow" ]] && {
NUM_LINES=`grep -v 'root' /etc/cron.allow | wc -l`
[[ "${NUM_LINES}" -ne "0" ]] && {
print_warning "users other than root exist in /etc/cron.allow"
} || :
} || {
print_warning "/etc/cron.allow does not exist"
}
[[ -e "/etc/cron.deny" ]] && {
cut -d: -f1 /etc/passwd | grep -v root | sort > /tmp/chkhard_cron_deny.$$
sort /etc/cron.deny > /tmp/chkhard_cron_deny_compare.$$
diff /tmp/chkhard_cron_deny.$$ /tmp/chkhard_cron_deny_compare.$$ >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "/etc/cron.deny not in sync with current system accounts"
} || :
} || {
print_warning "/etc/cron.deny does not exist"
}
[[ -e "/etc/at.deny" ]] && {
cut -d: -f1 /etc/passwd | grep -v root | sort > /tmp/chkhard_at_deny.$$
sort /etc/at.deny > /tmp/chkhard_at_deny_compare.$$
diff /tmp/chkhard_at_deny.$$ /tmp/chkhard_at_deny_compare.$$ >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "/etc/at.deny not in sync with current system accounts"
} || :
} || {
print_warning "/etc/at.deny does not exist"
}
}
# Function: check_rhosts_pam()
# Arguments: None
# Returns: Nothing
# Description: No rhosts support in pam, thanks!
check_rhosts_pam() {
for PAM_FILE in /etc/pam.d/*; do
grep "^[^#]*rhosts_auth" ${PAM_FILE} >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
print_warning "rhosts_auth enabled in ${PAM_FILE}"
}
done
}
# Function: check_passwd_files()
# Arguments: None
# Returns: Nothing
# Description: Check for correct ownership and permissions on user database files
check_passwd_files() {
PASSWD_PERMS=`stat /etc/passwd | grep '^Access: (' | sed -n 's/^Access: (\([0-7][0-7]*\)\/.*$/\1/p'`
SHADOW_PERMS=`stat /etc/shadow | grep '^Access: (' | sed -n 's/^Access: (\([0-7][0-7]*\)\/.*$/\1/p'`
GROUP_PERMS=`stat /etc/group | grep '^Access: (' | sed -n 's/^Access: (\([0-7][0-7]*\)\/.*$/\1/p'`
PASSWD_OWNER=`ls -ld /etc/passwd | awk '{print $3}'`
PASSWD_GROUP=`ls -ld /etc/passwd | awk '{print $4}'`
SHADOW_OWNER=`ls -ld /etc/shadow | awk '{print $3}'`
SHADOW_GROUP=`ls -ld /etc/shadow | awk '{print $4}'`
GROUP_OWNER=`ls -ld /etc/group | awk '{print $3}'`
GROUP_GROUP=`ls -ld /etc/group | awk '{print $4}'`
[[ "${PASSWD_PERMS}" != "0644" ]] && {
print_warning "/etc/passwd has incorrect permissions"
}
[[ "${PASSWD_OWNER}" != "root" ]] && {
print_warning "/etc/passwd has incorrect ownership"
}
[[ "${PASSWD_GROUP}" != "root" ]] && {
print_warning "/etc/passwd has incorrect group ownership"
}
[[ "${SHADOW_PERMS}" != "0400" ]] && {
print_warning "/etc/shadow has incorrect permissions"
}
[[ "${SHADOW_OWNER}" != "root" ]] && {
print_warning "/etc/shadow has incorrect ownership"
}
[[ "${SHADOW_GROUP}" != "root" ]] && {
print_warning "/etc/shadow has incorrect group ownership"
}
[[ "${GROUP_PERMS}" != "0644" ]] && {
print_warning "/etc/group has incorrect permissions"
}
[[ "${GROUP_OWNER}" != "root" ]] && {
print_warning "/etc/group has incorrect ownership"
}
[[ "${GROUP_GROUP}" != "root" ]] && {
print_warning "/etc/group has incorrect group ownership"
}
}
# Function: check_crontab_permissions()
# Arguments: None
# Returns: Nothing
# Description: Check that all crontab files are heavily locked down
check_crontab_permissions() {
ETC_CRONTAB_PERMS=`stat /etc/crontab | grep '^Access: (' | sed -n 's/^Access: (\([0-7][0-7]*\)\/.*$/\1/p'`
ETC_CRONTAB_OWNER=`ls -ld /etc/crontab | awk '{print $3}'`
ETC_CRONTAB_GROUP=`ls -ld /etc/crontab | awk '{print $4}'`
[[ "${ETC_CRONTAB_PERMS}" != "0400" ]] && {
print_warning "/etc/crontab has incorrect permissions"
}
[[ "${ETC_CRONTAB_OWNER}" != "root" ]] && {
print_warning "/etc/crontab has incorrect ownership"
}
[[ "${ETC_CRONTAB_GROUP}" != "root" ]] && {
print_warning "/etc/crontab has incorrect group ownership"
}
CRONTAB_DIR_PERMS=`stat /var/spool/cron | grep '^Access: (' | sed -n 's/^Access: (\([0-7][0-7]*\)\/.*$/\1/p'`
CRONTAB_DIR_OWNER=`ls -ld /var/spool/cron | awk '{print $3}'`
CRONTAB_DIR_GROUP=`ls -ld /var/spool/cron | awk '{print $4}'`
[[ "${CRONTAB_DIR_PERMS}" != "0400" ]] && {
print_warning "/var/spool/cron has incorrect permissions"
}
[[ "${CRONTAB_DIR_OWNER}" != "root" ]] && {
print_warning "/var/spool/cron has incorrect ownership"
}
[[ "${CRONTAB_DIR_GROUP}" != "root" ]] && {
print_warning "/var/spool/cron has incorrect group ownership"
}
for CRONTAB in /var/spool/cron/*; do
CRONTAB_PERMS=`stat ${CRONTAB} | grep '^Access: (' | sed -n 's/^Access: (\([0-7][0-7]*\)\/.*$/\1/p'`
CRONTAB_OWNER=`ls -ld ${CRONTAB} | awk '{print $3}'`
CRONTAB_GROUP=`ls -ld ${CRONTAB} | awk '{print $4}'`
[[ "${CRONTAB_PERMS}" != "0400" ]] && {
print_warning "${CRONTAB} has incorrect permissions"
}
[[ "${CRONTAB_OWNER}" != "root" ]] && {
print_warning "${CRONTAB} has incorrect ownership"
}
[[ "${CRONTAB_GROUP}" != "root" ]] && {
print_warning "${CRONTAB} has incorrect group ownership"
}
done
}
# Function: check_ftpusers()
# Arguments: None
# Returns: Nothing
# Description: Ensure that FTP access is locked down
check_ftpusers() {
for FTPFILE in /etc/ftpusers /etc/vsftpd.users; do
[[ -e "${FTPFILE}" ]] && {
diff <(sort ${FTPFILE}) <(cut -d: -f1 /etc/passwd | sort) >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "${FTPFILE} not in sync with system accounts"
} || :
} || {
print_warning "${FTPFILE} does not exist"
}
done
}
# Function: check_xserver()
# Arguments: None
# Returns: Nothing
# Description: If X is installed, ensure it is not listening for tcp connections
check_xserver() {
for XFILE in /etc/X11/xdm/Xservers /etc/X11/gdm/gdm.conf /etc/X11/xinit/xserverrc; do
[[ -e "${XFILE}" ]] && {
print_warning "${XFILE} exists - X servers are NOT permitted"
grep '^[^#]*-nolisten tcp' ${XFILE} >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "${XFILE} does not contain -nolisten tcp"
} || :
} || :
done
}
# Function: check_core_dumps()
# Arguments: None
# Returns: Nothing
# Description: Ensure that core dumping is disabled
check_core_dumps() {
[[ -e "/etc/security/limits.conf" ]] && {
grep '^.*\*.*soft.*core.*0' /etc/security/limits.conf >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "soft core limit not set in /etc/security/limits.conf"
} || :
grep '^.*\*.*hard.*core.*0' /etc/security/limits.conf >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "hard core limit not set in /etc/security/limits.conf"
} || :
} || {
print_warning "/etc/security/limits.conf does NOT exist"
}
}
# Function: check_pam_other()
# Arguments: None
# Returns: Nothing
# Description: Check that /etc/pam.d/other contains required lockdown
check_pam_other() {
[[ -e "/etc/pam.d/other" ]] && {
# Very rough check... assumes a lot
LINECOUNT=`grep '^[^#]' /etc/pam.d/other | grep -v '^[ ]$' | wc -l`
[[ "${LINECOUNT}" -ne "8" ]] && {
print_warning "/etc/pam.d/other does not have the eight required rules"
} || :
} || {
print_warning "/etc/pam.d/other does not exist"
}
}
# Function: check_ctrl_alt_del()
# Arguments: None
# Returns: Nothing
# Description: Check that CTRL-ALT-DEL sequence won't reboot our server!
check_ctrl_alt_del() {
grep '^ca::ctrlaltdel.*$' /etc/inittab >/dev/null 2>&1
[[ "$?" -eq "0" ]] && {
print_warning "CTRL-ALT-DEL sequence still enabled in /etc/inittab"
}
}
# Function: check_sulogin()
# Arguments: None
# Returns: Nothing
# Description: Make sure that single user login will require a password
check_sulogin() {
grep '^~~:S:wait:/sbin/sulogin$' /etc/inittab >/dev/null 2>&1
[[ "$?" -ne "0" ]] && {
print_warning "sulogin not configured in /etc/inittab"
}
}
# main()
check_os
check_root
check_fstab
check_required_software
display_messages
display_services
check_system_accounts
check_syslog_conf
check_sysctl_conf
check_sendmail
check_sshd_config
check_su
check_symlinks
check_stack_protection
check_umask
check_mesg
check_system_auth
check_securetty
check_cron_access
check_rhosts_pam
check_passwd_files
check_ftpusers
check_xserver
check_core_dumps
check_pam_other
check_ctrl_alt_del
check_sulogin
cleanup
# ToDo:
# - setuid/setgid/world-writable files
# - check home directory permissions
exit 0