Gentoo Forums
Gentoo Forums
Gentoo Forums
Quick Search: in
HOWTO: Prevent attempts at SSH bruteforcing
View unanswered posts
View posts from last 24 hours

 
Reply to topic    Gentoo Forums Forum Index Documentation, Tips & Tricks
View previous topic :: View next topic  
Author Message
SoTired
Apprentice
Apprentice


Joined: 19 May 2004
Posts: 174

PostPosted: Thu Dec 02, 2004 4:39 am    Post subject: HOWTO: Prevent attempts at SSH bruteforcing Reply with quote

In case anyone ever sees this post: There is now a far better iptables way to prevent ssh bruteforcing.

Code:
-A INPUT -p tcp --dport 22 -m state --state ESTABLISHED -j ACCEPT
-A INPUT -p tcp --dport 22 -m recent --update --seconds 60 -j DROP
-A INPUT -p tcp --dport 22 --tcp-flags SYN,ACK,RST, SYN -m state --state NEW -m recent --set -j ACCEPT


Something along the lines of that should allow only one ssh connection attempt per minute - change the --seconds 60 to whatever you deem secure. My script (below) is obsolete.



Overview

Well most anyone who runs a SSH server open to the internet is probably plagued by tons of SSH break-in attempts - I know personally one of my boxes sometimes gets upwards of a few thousand attempts per day.

The normal procedures for fixing this problem are to do one of the following:
  • Enforce a strong password policy so the login attempts are nothing to worry about (or, better yet, as nightblade suggests, use certificates)
  • Only allow SSH access to a restricted set of IPs
  • Restrict SSH logins to specific users (to be fair this does not really entirely mitigate the problem)
  • As nightblade also suggests, running the SSH server on a port aside from 22 can also be rather effective
  • (Possibly more - tell me and I'll add them)

So, if it's possible to implement any of these, then it would be a good idea to do so; and hence to not use this script. However, sometimes none of these things are possibly (because of various circumstances, I'm sure everyone has different reasons,) and that's where this script comes into play. It parses a log file containing failed login attempts every few (this is configurable) minutes and temporarily blocks the offending IP address.

This method has a couple of drawbacks:
  • Must constantly be parsing the logs, blocking new IPs, this takes CPU time
  • Can lock someone out, temporarily, just because they typed their password wrong (you can configure how many times before it blocks, however)

The way the script works is:
  • Parses through a log file.
  • Drops all previously blocked IP address (from the last parse & block cycle.)
  • Blocks all IP addresses that have more than x failed SSH login attempts in the past y minutes.


A weakness is that if a person continues to attempt to log into the SSH server after being blocked these attempts will not be logged (as the packets are being dropped at the firewall,) therefore, after 'y' minutes they will be able to continue to attempt to log in for another 'y' minutes until they are blocked again. At first this seems to be a fatal flaw, however most attacks tend to stop after the five minutes of blocking - and, with human attackers there is always a chance they might give up. I do, however, plan on adding to the script in order to allow it to parse a specifically crafted iptables log message to extend blocking.

Setup

Instead of writing some kind of guide to setting up and using the script, I'm just going to outline it's pieces and give an example of what I would do to get it to work. Everything is pretty much straight forward, so hopefully this will be adequate.

You must be running the following software in order to use this script:
  • SSH (of course)
  • Bash
  • Iptables (it may work for ipchains too, not sure)
  • A syslogger - the output of failed SSH logins must fit this regex:
    Code:
    ^[A-Za-z]+.*\ 30+\ 12+:.+sshd\(pam_unix\).*authentication\ failure;.+$
    30 and 12 would change based on the current hour and day respectively

  • A cron daemon

The script is comprised of two files. There is a bash component and a c component. (Both are really simple, so if you want to make them better or something do whatever you want.) The bash component parses through the log file, retrieves possible candidates for blocking and then sends them to the c portion to parse through and see if they are recent enough to be blocked.

Here are my steps for setting up the script:
Code:
wget http://users.wpi.edu/~chanin/sshsafe.tar.gz
tar -zxf sshsafe.tar.gz
mv sshsafe sshsecparse.c /usr/local/sbin
cd /usr/local/sbin
vi sshsecparse.c
vi sshsafe
gcc -O2 sshsecparse.c -o sshsecparse
chmod +x sshs*
cat /dev/null > /var/log/messages
sshsafe /var/log/messages
crontab -e


In sshsecparse.c:
Code:
const int often = 5;  should be changed so that 'often' is how often the script is going to be run by cron


In sshsafe:
Code:
BlockAfter=4  should be changed to how many times someone must fail at logging in to be blocked
Log=/dev/null  should be changed to where you want the blocks to be logged, if you want them logged


For crontab -e (I use vixie-cron, this may be different for different cron daemons):
Code:
*/5   * * * *   /usr/local/sbin/sshsafe /var/log/messages >/dev/null
(Note the */5, the 5 should be changed to the same number as often in sshsecparse.c)



The Code

Here's the bash portion:
Code:
#!/bin/bash

# How many attempts a person must make in the time interval before they are blocked
BlockAfter=4

#Where to log blocks, /dev/null for no logging
Log=/var/log/sshsafe

if [ -z "$1" ]
then
  echo "Temp iptables blocks SSH bruteforcers."
  echo "  usage:"
  echo "  sshsecure authlogfile"
  exit 1
fi

total=0
#Adds an IP to iptables, only blocks it to port 22, after BlockAfter infractions
function blocker()
{
  if [ "$1" == "$last" ]
  then
    total="$((total+1))"
  else
    total=0
  fi
  #Block after 4 attemps over x minutes
  if [ "$total" -eq "$BlockAfter" ]
  then
    let "blocked = $blocked + 1"
    echo "Blocking $1"
    echo "Blocking $1" >> "$Log"
    iptables -I INPUT -p tcp -s $1 --destination-port 22 -j DROP
  fi
  last=$1
}

echo "Dropping all old blocks..."
rtd=1
while [ $rtd -le `cat /var/lib/iptables/tempblocks` ]
do
  iptables -D INPUT 1
  let "rtd = $rtd + 1"
done
cat /dev/null > /var/lib/iptables/tempblocks
blocked=0

echo "Getting intrusion attempts..."
ctim=`date | awk '{ print $4 }'`
cday=`date | awk '{ print $3 }'`
chour=`echo $ctim | awk -F : '{ print $1 }'`
cminute=`echo $ctim | awk -F : '{ print $2 }'`
rday=`expr $cday - 1`
if [ "$chour" -eq 0 ]
then
  rhour=23
else
  rhour=`expr $chour - 1`
fi
if [ "$cday" -eq 1 ]
then
  rday=1
fi
tandhi=(`egrep "^[A-Za-z]+.*\ (\$cday|\$rday)+\ (\$chour|\$rhour)+:.+sshd\(pam_unix\).*authentication\ failure;.+$" "$1" | awk '{ print $13"|"$3"|"$2 }'`)

echo "Sorting intrusion attempts list..."
#Sort the list
h=1
hh=1
n=${#tandhi[*]}
while [ "$h" -lt "$n" ]
do
  hh="$((hh+h))"
  h="$((hh-h))"
done
while [ "$hh" -gt 1 ]
do
  for (( i = h ; i < n ; i++ ))
  do
    v="${tandhi[$i]}"
    j="$i"
    while [ "$j" -ge "$h" -a @"${tandhi[$((j-h))]}" \> "$v" ]
    do
      tandhi[$j]="${tandhi[$((j-h))]}"
      j="$((j-h))"
    done
    tandhi[$j]="$v"
  done
  h="$((hh-h))"
  hh="$((hh-h))"
done

echo "Calculating blocks ($n attempts to consider)..."
index=0
while [ "$index" -lt "$n" ]
do
  if [ "`sshsecparse $cday $chour $cminute "${tandhi[$index]}"`" == 1 ]
  then
    host=`echo ${tandhi[$index]} | awk -F \| '{ print $1 }' | awk -F = '{ print $2 }'`
    blocker $host
  fi
  index="$((index+1))"
done

echo "$blocked" > /var/lib/iptables/tempblocks
echo "Done!"
exit 0


Here's the C portion:
Code:

// Change this to how often you will cron the script
// ex. 5 = every minutes
const int often = 5;

#include "stdio.h"
#include "string.h"

void usage();
int readinput(char* cday, char* chour, char* cminute, char* string);

int main(int argc, char *argv[])
{
  if (argc != 5)
  {
    usage();
    return(0);
  }

  // No idea why bash script wont work without this either
  if(readinput(argv[1], argv[2], argv[3], argv[4]) == 1)
  {
    return(1);
  } else {
    return(0);
  }
}

// Just to be helpful
void usage()
{
  fprintf(stderr, "sshsecparse - Checks an auth log message and times to\ndetermine if it is a recent infraction or not.\n");
  fprintf(stderr, "This program is supposed to be called as part of sshsafe.\n");
  fprintf(stderr, "\nusage:\n");
  fprintf(stderr, "   sshsecparse day hour minute rhost=IPADDR|HR:MN:SC|DAY\n");
}

// Function that does everything
int readinput(char* cday, char* chour, char* cminute, char* string)
{
  // Grab the times
  int rday = atoi(cday);
  int rhour = atoi(chour);
  int rmin = atoi(cminute);

  // Extract the incident time values
  strtok(string, "|"); // First is the IP, ignore it
  char* time = strtok(NULL, "|");
  int day = atoi(strtok(NULL, "|"));
  int hour = atoi(strtok(time, ":"));
  int min = atoi(strtok(NULL, ":"));

  // Check to see if this incident happened within past often minutes...
  if((rhour == hour && (rmin-min) <= often) ||
     (rhour == (hour-1) && (rmin-min) <= (often-60)) ||
     (rday == (day-1) && rhour == (hour-23) && (rmin-min) <= (often-60)))
  {
    fprintf(stdout, "1"); // I have no idea why the bash script wont work without this
    return(1);
  }
  return(0);
}


Here's a link where you can get both portions in a gzipped tarball:
http://users.wpi.edu/~chanin/sshsafe.tar.gz


Errata
  • I had initially written about the possibilty of someone spoofing a known-good ip and getting them blocked by this script. nightblade pointed out below, however, that this is not nearly as trivial as I had thought - instead of simply spoofing the IP, they must also guess the TCP sequence numbers. A good tip for helping with those would be Grsecurity which adds an option for truly random sequence numbers.
  • Do not change your firewall rules while this script is running (is in cron) - as a matter of fact, I would suggest you back up your firewall rules before using it.
  • This script has not undergone extensive testing. Feel free to ask me to fix it, or fix it yourself it wont work with what you're running.
  • It would also be prudent to note that most of this was thought up/coded entirely while very tired. There's probably a better way to do most of the things this script does - but if you want a prewritten solution it may suffice.
  • I know the C code has a lot of anomalies in it. I've never had to do anything with bash interacting with C before and that's what I ended up with, it sucks, yet works.
  • If you're concerned about the speed of the script, my testing shows that it generally takes less than 3 seconds on my 200 MHZ Pentium 1 (which is quite a step up from the 20 second bash-only version ;)).


Last edited by SoTired on Fri Aug 26, 2005 11:12 pm; edited 5 times in total
Back to top
View user's profile Send private message
at6
Tux's lil' helper
Tux's lil' helper


Joined: 28 Nov 2002
Posts: 78
Location: /dev/null

PostPosted: Thu Dec 02, 2004 12:10 pm    Post subject: Reply with quote

Great job!

i've been searching a while for such a good solution to get rid of those annoying ssh-breakin attempts.

cheers,

marc
_________________
debian: stable but lame! suse: unstable and lame! gentoo: stable and only 4 geeks!
Gadget tests!
Back to top
View user's profile Send private message
nightblade
Guru
Guru


Joined: 20 Jul 2004
Posts: 368
Location: back from SE Asia

PostPosted: Thu Dec 02, 2004 2:11 pm    Post subject: Reply with quote

Great job, SoTired.

Just a couple of comments:
Quote:
Enforce a strong password policy

Or, even better, use certificates.
Quote:
Et Cetera (Anybody know of more?)

Running the ssh server on a non-standard port is usually enough to mislead 95% of break-in attempts. We are dealing, for the most part, with dumb bots that keep on scanning only port 22.
Quote:
somewhat vulnerable to someone spoofing a known IP address and preventing them from accessing the SSH server

Since you need a full TCP connection in order to trigger the IP block, you need not only to spoof the source address but also to guess the server generated sequence numbers. A sophisticated attack which makes this drawback something not to worry too much about :)

Cheers !
_________________
In God we trust. All the others must provide a valid X.509 certificate
Back to top
View user's profile Send private message
etnoy
Apprentice
Apprentice


Joined: 29 Aug 2003
Posts: 255
Location: Västerås, Sweden

PostPosted: Fri Dec 03, 2004 7:12 am    Post subject: Reply with quote

Did I understand it correctly that a user gets blocked if four failed login attempts are made? That would make it really easy to perform DoS attacks, you just serve them the vulnerability on a silver plate.
_________________
The md5sum of the above post is 06280ccd85ef9deb49c336e7945f4b5c

God is dead! - Nietzsche
Nietzsche is dead! -God
Back to top
View user's profile Send private message
SoTired
Apprentice
Apprentice


Joined: 19 May 2004
Posts: 174

PostPosted: Fri Dec 03, 2004 7:47 am    Post subject: Reply with quote

etnoy wrote:
Did I understand it correctly that a user gets blocked if four failed login attempts are made? That would make it really easy to perform DoS attacks, you just serve them the vulnerability on a silver plate.


No, an IP address will be blocked after a configurable number of failed login attempts.
Back to top
View user's profile Send private message
Display posts from previous:   
Reply to topic    Gentoo Forums Forum Index Documentation, Tips & Tricks All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum