View previous topic :: View next topic |
Author |
Message |
SoTired Apprentice
Joined: 19 May 2004 Posts: 174
|
Posted: Thu Dec 02, 2004 4:39 am Post subject: HOWTO: Prevent attempts at SSH bruteforcing |
|
|
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 |
|
|
at6 Tux's lil' helper
Joined: 28 Nov 2002 Posts: 78 Location: /dev/null
|
Posted: Thu Dec 02, 2004 12:10 pm Post subject: |
|
|
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 |
|
|
nightblade Guru
Joined: 20 Jul 2004 Posts: 368 Location: back from SE Asia
|
Posted: Thu Dec 02, 2004 2:11 pm Post subject: |
|
|
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 |
|
|
etnoy Apprentice
Joined: 29 Aug 2003 Posts: 255 Location: Västerås, Sweden
|
Posted: Fri Dec 03, 2004 7:12 am Post subject: |
|
|
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 |
|
|
SoTired Apprentice
Joined: 19 May 2004 Posts: 174
|
Posted: Fri Dec 03, 2004 7:47 am Post subject: |
|
|
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 |
|
|
|
|
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
|
|