webSIGHTdesigns - Web Design, Web Development, Web Hosting

How to Block SSH Probes

Posted on Saturday, November 29th, 2014 at 6:32 pm
by webSIGHTdesigns

Block those pesky SSH probes with some custom Bash-Fu.

Alien ProbeNo one likes having their SSH server probed for unauthorized access. So why not ban the IP addresses which commit these acts? This post describes how to do just that, using some simple Bash scripts and running them periodically with Cron.

These scripts require iptables currently, but could probably be modified to support other firewall solutions, assuming they use text-based configurations to specify what IPs should be blocked.

One of the biggest requirements of our block script is that it not run if an instance is already running, so that the script doesn't take up precious server resources. To accomplish this, we use a file lock in /tmp to determine if the script is already running.

Another important thing is to not set your Cron job to run the script too often. In our examples we run the script every 30 minutes. If the server you're running the script on is a high traffic server you may want to run the script more often, but there's probably no reason to run it more often than every 30 minutes.

Automated Banning and Unbanning

Let's take a look at the code. Our first script runs every 30 minutes to set the block commands in iptables for each IP address. We named ours ssh-block.sh:

#!/bin/bash

awk=/usr/bin/awk
sort=/usr/bin/sort
grep=/usr/bin/grep
uniq=/usr/bin/uniq
sed=/usr/bin/sed
logger=/usr/bin/logger
logfile=/var/log/messages
iptables=/usr/sbin/iptables
rules=/etc/iptables.rules
blockfile=/etc/iptables.rules.block
badcount="5"
restart=0
curdate=$(date +"%Y-%m-%d")

# use file descriptor 9 with flock
exec 9>/tmp/sshblock.lock
# flock ties a file descriptor to a lock file
if ! flock -n 9  ; then
  # script is already running, log an error message
  $logger "[ssh-block] cannot acquire lock, giving up"
  exit 1
else
  $logger "[ssh-block] starting"
  # script is not already running, check for IPs to block
  $grep 'Failed password for invalid user' $logfile | $awk '{ a=NF-3; print $a}' | $uniq -c | $sed 's/^ *//' | $sort -n |
  {
    while read i
    do
      # count how many failed password attempts
      count=`echo $i | cut -d " " -f1`
      # set the IP address into a variable
      ipaddr=`echo $i | cut -d " " -f2`
      # set a variable that checks if the IP address is already in the block list
      inlist=`$grep $ipaddr $blockfile | $grep "DROP"`
      if [[ -z "$inlist" && "$count" -ge "$badcount" ]]
      then
        # ban the IP in iptables
        echo "$iptables -A eth0-in -s $ipaddr/255.255.255.255 -d 0.0.0.0/0.0.0.0 -j DROP # $curdate" >> $blockfile
        restart=1
        # log a message to the log file
        $logger "[ssh-block] auto-banning IP address: $ipaddr"
      fi
    done

    # if restart variable is greater than or equal to 1, run the iptables rules
    if [[ "$restart" -ge 1 ]]
    then
      $rules
      $logger "[ssh-block] restarting iptables ($rules)"
    else
      $logger "[ssh-block] there are no addresses that require blocking"
    fi
  }
  $logger "[ssh-block] stopping"
fi

As you can see there are some customizable options at the top of the script. Most of these should be pretty standard accross Linux distributions but you can change the path to the executables if your system is set up differently. The last two, restart and curdate, should not be changed. The badcount variable sets how many failed login attempts our script needs to see before it blocks an IP address.

To run this script every 30 minutes, we set a line in our crontab:

# block ssh root probes (runs every 30 minutes)
*/30 * * * * /bin/bash /path/to/ssh-block.sh

Set the path to your ssh-block.sh script in the crontab line and save your changes.

Our next script removes the blocked IP addresses after 6 months. We named ours ssh-unblock.sh:

#!/bin/bash

grep=/usr/bin/grep
uniq=/usr/bin/uniq
sed=/usr/bin/sed
cut=/usr/bin/cut
logger=/usr/bin/logger
rules=/etc/iptables.rules
blockfile=/etc/iptables.rules.block
curdate=$(date +"%Y-%m-%d")
fcurdate=$(date +"%m/%d/%Y")
expdate=$(date '+%Y-%m-%d' --date="$fcurdate 6 month ago")
expdatesec=$( date -d "$expdate" +%s )
restart="0"

# use file descriptor 9 with flock
exec 9>/tmp/sshunblock.lock
# flock ties a file descriptor to a lock file
if ! flock -n 9  ; then
  # script is already running, log an error message
  $logger "cannot acquire lock, giving up: /root/ssh-unblock.sh"
  exit 1
else
  $logger "[ssh-unblock] starting"
  # script is not already running, check dates of IPs to unblock
  $grep ' # ' $blockfile  | $cut -d" " -f11 | $uniq |
  {
    while read i
    do
      # if date is earlier than expire date
      isec=$( date -d "$i" +%s )
      if (( $isec < $expdatesec )) ; then
        # remove lines with this date from the block file
        $sed -i "/$i/d" $blockfile
        restart="1"
      fi
    done
    # if restart variable is greater than or equal to 1, run the iptables rules
    if [[ "$restart" -ge "1" ]]
    then
      $rules
      $logger "[ssh-unblock] restarting iptables ($rules)"
    fi
  }
  $logger "[ssh-unblock] stopping"
fi

By default, it unblocks IP addresses after 6 months. To change this value, edit this line in ssh-unblock.sh:

expdate=$(date '+%Y-%m-%d' --date="$fcurdate 6 month ago")

For instance, you could specify 1 year ago instead of 6 month ago in the above line to unblock after 1 year.

To run this script once per day at midnight, we set a line in our crontab:

# unblock ip bans older than 6 months (runs every night at midnight)
0 0 * * * /bin/bash /path/to/ssh-unblock.sh

Our script uses the logger command to submit logs to the messages log file. You may need to install logger for this functionality if your system doesn't already have it installed.

Command Line Banning and Unbanning

Optionally, you can also set up some simple commands to allow you to ban/unban IP addresses quickly with a simple command such as:

$ banip 192.168.1.1
$ unbanip 192.168.1.1

On most systems we save our scripts to the /usr/sbin/ directory. Our banip script contains:

#!/bin/sh
 
iptables=/usr/sbin/iptables
blockfile=/etc/iptables.rules.block
rules=/etc/iptables.rules
logger=/usr/bin/logger
curdate=$(date +"%Y-%m-%d")
 
if [ "$1" != '' ]; then
  echo "$iptables -A eth0-in -s $1/255.255.255.255 -d 0.0.0.0/0.0.0.0 -j DROP # $curdate" >> $blockfile
  $rules
  $logger "banning IP $1"
elif [ "$1" = 'help' ] || [ "$1" = '' ]; then
  echo "usage:"
  echo "banip "
fi

Our unbanip script is very similar:

 #!/bin/sh
 
sed=/usr/bin/sed
blockfile=/etc/iptables.rules.block
rules=/etc/iptables.rules
logger=/usr/bin/logger
 
if [ "$1" != '' ]; then
  $sed -i "/$1/d" $blockfile
  $rules
  $logger "unbanning IP $1"
elif [ "$1" = 'help' ] || [ "$1" = '' ]; then
  echo "usage:"
  echo "unbanip "
fi

Make sure to chmod +x the banip and unbanip files to ensure they are executable.

If you have any improvements or comments, let us know in the comments below.

Please Sign In

Please sign in to post a comment.

Web Development

View details »

Web Hosting

View details »

Our Portfolio

View portfolio »

WebSight Designs webSIGHTdesigns preferred email webSIGHTdesigns United States United States