I manage a Linux server (Redhat EL4, kernel version 2.6.9-34). Yesterday, I found the server was attacked relentlessly from 121.101.223.211. It seemed to be a brutal force dictionary attack. What to do then? The obvious way is to manually add that IP to host.deny, that is, append “sshd: 121.101.223.211” to /etc/host.deny in a separate line.
But that is so un-Linux and so unsustainable.
It was time to excise my bash-fu and sed-fu. The attacks were logged in /var/log/messages and looked like this (part):
sshd(pam_unix)[6548]: authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=121.101.223.211 user=root
The idea is to periodically check (hourly by cron) the log file and find IPs with a large number (≥ 16) of unsuccessful SSH login attempts. With the following line, the IPs can be parsed out, sorted, and counted:
sed -n "/${SERVICE}.*authentication failure/ s/^.*rhost=\([0-9.]*\).*$/\1/p" "${LOGFILE}" | sort | uniq -c
where SERVICE=sshd and LOGFILE=/var/log/messages. The output is like this:
19 112.187.163.188 140 121.101.223.211 45 211.139.10.219
The leading numbers are counts of unsuccessful SSH login attempts, the latter are corresponding IPs. Then echo the IPs to /etc/host.deny if they are not there yet and their counts are no less than 16. Of course, I should also protect FTP service (vsftpd). Not too hard, ha!
The following is the complete bash script (ip_block). Put it into /etc/cron.hourly and it will work for you 24/7, for free!
#! /bin/bash # add ips with more invalid login attempts than permitted to hosts.deny # usage: ip_block [invalid login limit, default to 16] if [ "$EUID" -ne 0 ] then echo " You must be root to run this script!" exit -1 fi SYSLOG_FILE="/var/log/messages" HOSTDENY_LIST="/etc/hosts.deny" [ "$#" -ge "1" ] && LOGIN_LIMIT="$1" || LOGIN_LIMIT=16 function add_to_list () { SERVICE="$1" LOGFILE="$2" LSTFILE="$3" MAXCOUNT="$4" sed -n "/${SERVICE}.*authentication failure/ s/^.*rhost=\([0-9.]*\).*$/\1/p" "${LOGFILE}" | sort | uniq -c | while read line do count=`echo $line | sed 's/ *.*$//'` ip=`echo $line | sed 's/[0-9]* *//'` n=`grep -c -e "${SERVICE}: *$ip" "${LSTFILE}"` [ "$count" -ge "$MAXCOUNT" -a "$n" -eq "0" ] && echo "${SERVICE}: $ip" >> "${LSTFILE}" done } add_to_list sshd "${SYSLOG_FILE}" "${HOSTDENY_LIST}" $LOGIN_LIMIT add_to_list vsftpd "${SYSLOG_FILE}" "${HOSTDENY_LIST}" $LOGIN_LIMIT