Since 2004 we've seen an increase in the number of brute force attempts to break into machines via ssh. These attempts appear to use the same password across different accounts, and thus do not trigger many of the normal methods of detecting brute force, such as pam_tally, nor do they result in the typical slowdowns associated with repeated attempts at a single ID.
Murray Anderegg turned me onto timelox, written by a fellow named Elad Efrat (aka Brian at ethernet.org). This code tracks failed logins by ip number, but doesn't care about which ids were hit, and when a threshold is reached, the offending ip number is put into a deny rule for a firewall.
The one thing I didn't like about it so much was that the code called the firewall explicitly from within ssh, so you'd have to change the patch code for different oses. And Murray wanted to make some changes in the source code to meet his needs with iptables under linux. So what we did was modify Brian's patch code and abstracted things a bit so that instead of calling a firewall directly, you can set a variable in the sshd_config file specifying a script that's called when the threshold is reached. I _think_ it's relatively safe, but I'm not that good at this kind of thing, so your mileage may vary. I can't be responsible if you use this code and something bad happens, whether that is someone hacking your machine, locusts attacking your rose bushes, or not-quite-flying cattle impacting your home in an adverse manner.
This particular doc is primarily oriented to OS X, but the timelox code works fine on Redhat and probably pretty much an flavor of linux, and there's an example script for iptables provided.
Well, partly to keep things portable, and partly since I think calling an external script is a more attractive option, since that keeps the patching of ssh to a minimum, since the same patch can be used for pretty much any operating system.
Also, although this example uses a timelox patch in ssh to call our scripts, one can use any manner of software as a trigger. For example, one could make a honeypot program to watch a socket and mimic ftp or other services, and call this script when anything odd is seen. Or one could call a completely different script to fire a report, trigger a firewall at the head of a network, etc.
Please note that the patches and scripts were updated on 02/02/08, the changes are pretty significant including changes in path names and script names in the default installation.
The relevent files are here.
Also, do note that a particular version of openssh may not be ideal for your needs, so what you may wish to do is find out which version of openssh your distribution runs, and adapt the patch code to the source for that version. So test it throughly before deploying, and if you run into problems, you might consider adapting the patch to whatever version of openssh your system uses. Adapting the patch is pretty easy, especially if you consult Brian's original patch to compare the contexts.
In a basic installation there are four steps:
Instructions are available for:
Once openssh is installed, you need to configure it. So after you make a backup copy, open the sshd_config file with a text editor, for example, vi:
sudo cp /usr/local/etc/sshd_config /usr/local/etc/sshd_config.orig
sudo vi /usr/local/etc/sshd_config
There are several variables that need to be set in the sshd_config file. On OS X systems the first thing to do is enable PAM support, find the line that says "#UsePAM no" and change that to:
UsePAM yes
You also need to check out the timelox settings. Here's a list of timelox variables:
All of these are just string or integers, so you can use them for other purposes if you like.
In this example, the number of bad logins and the time frame are both set relatively high, which is what I think makes sense for a workstation. These lines can go anywhere, so just scroll to the bottom of the file and add them there. Here are reasonable starting values
UseTimelox yes
TimeloxTimeFrame 7200
TimeloxMaxBadLogins 3
TimeloxCall /usr/local/libexec/TheHand.sh
TimeloxPath /usr/local/etc/timelox/
TimeloxIPTable INPUT
TimeloxRuleno 1
TimeloxLogLevel authpriv.info
TimeloxLabel sshd_timelox
While you're here you may want to make some other changes. Adding a banner is a good idea, if only to help you with your testing. Find the banner line and uncomment it:
Banner /usr/local/etc/sshd_banner
I also strongly suggest you disable root login--on the mac root is disabled by default, but some folks do enable it, and there's no reason to log in directly with root (at least with a password--there are some reasons to allow root logins with a key if your writing scripts to control a machine remotely, but most folks don't do that). If you like to enable root, you can login as yourself and su to root.
PermitRootLogin no
Don't forget to save the file!
In the example above, timelox is calling a script named TheHand.sh. This script is a bash shell script that takes variables, such the ip number and a text string used to indicate the source of the block. There are two sample scripts provided, one TheHand.sh_ipfw for Mac OS X, and TheHand.sh_iptables for redhat. Both use pretty much the same logic. The example for redhat is set up in a brutal fashion to insert rules at the beginning of the INPUT chain. This method works, but it's not elegant. The ipfw version assumes that you are using a custom firewall, and inserts rules starting at 5000. The way to use this is to set up ipfw so that local host rules are in the lower number ranges, and then put in services acceptance rules starting high (in my case, 25000). That way the deny rule inserted by timelox stays clear of your rules, and the denys occur before the service acceptance rules.
TheHand.sh does some basic sanity checks on the ip number to see that it's valid (currently only ipv4 addresses work) and then inserts a rule denying all traffic inbound from that ip address into the firewall. TheHand.sh also supports logging. There are comments throughout the script that explain pretty well what it does, and it's not very complicated. You should read through the file to make sure you understand (at least roughly) what it's doing.
Mac OS X:
sudo cp ./TheHand.sh_ipfw /usr/local/libexec/TheHand.sh
sudo chmod u+x /usr/local/libexec/TheHand.sh
Redhat (as root):
cp ./TheHand.sh_iptables /usr/local/libexec/TheHand.sh
chmod u+x /usr/local/libexec/TheHand.sh
You also need to make a place for timelox to write files. You can choose pretty much any location, but you want to make it so that only root can write there. I'm using /usr/local/etc/timelox, so here's the command to make the directories on a mac (on redhat, it's the same, but you'd su to root and not use the sudo command):
sudo mkdir /usr/local/etc/timelox
Ok, now that the bits should all be in place, time for some testing. To test our sshd installation, we'll start it with some flags in dbug mode on a non-standard port.
sudo /usr/local/sbin/sshd -p 2222 -d
You'll see something like this:
[Avatar:/usr/local/bin] hays% sudo /usr/local/sbin/sshd -p 2222 -d
debug1: sshd version OpenSSH_4.0p1
debug1: private host key: #0 type 0 RSA1
debug1: read PEM private key done: type RSA
debug1: private host key: #1 type 1 RSA
debug1: read PEM private key done: type DSA
debug1: private host key: #2 type 2 DSA
debug1: rexec_argv[0]='/usr/local/sbin/sshd'
debug1: rexec_argv[1]='-p'
debug1: rexec_argv[2]='2222'
debug1: rexec_argv[3]='-d'
debug1: Bind to port 2222 on ::.
Server listening on :: port 2222.
debug1: Bind to port 2222 on 0.0.0.0.
Server listening on 0.0.0.0 port 2222.
Generating 768 bit RSA key.
RSA key generation complete.
Now, in a different terminal window, try logging into localhost on port 2222, with:
ssh -p 2222 127.0.0.1
You should see some debugging code fly by in the window in which you started sshd, and something like this in the window you're logging in from:
[Avatar:~] hays% ssh -p 2222 127.0.0.1
******************************************
* *
* Warning! Unathorized Logins Prohibited *
* *
******************************************
Password:
Notice the banner--since we set this in the /usr/local/etc/sshd_config, we know we're using the right config file (and not the default mac /etc/ssh_config). Give it your password and see if you can login. If you succeed, you've got a working sshd. Type exit, and you'll leave that ssh session and fall back into your terminal window. If you look at the other window, where you started sshd, you'll should have the prompt back. This method of running sshd in debug mode only sets up a single session, so to run another test, you have to restart it (but that's easy, just use the up arrow key to back up one in the cammand history and then hit return).
Now, restart the sshd login into a remote machine, and then try to ssh back to the machine with the new sshd installed, using the same port. This time, munge your password. What should happen is that you get three attempts, and the fourth will appear to lockup (if this worked, then when you try the fourth time, your machine with timelox will insert a firewall rule blocking all traffic from the remote machine). That block will stay in place until you reload your firewall or reboot.
Once you've tested your copy of sshd, you'll likely want to set up the system so that it will use that version instead of the one that shipped with the operating system. On OS X, you do that by editing the sshd-keygen-wrapper script.
sudo cp /usr/libexec/sshd-keygen-wrapper sshd-keygen-wrapper.orig
sudo vi /usr/libexec/sshd-keygen-wrapper
so that it contains the following lines (basically, pointing to the new sshd locations):
#!/bin/sh
[ ! -f /usr/local/etc/ssh_host_key ] && ssh-keygen -q -t rsa1 -f /usr/local/etc/ssh_host_key -N "" -C ""
[ ! -f /usr/local/etc/ssh_host_rsa_key ] && ssh-keygen -q -t rsa -f /usr/local/etc/ssh_host_rsa_key -N "" -C ""
[ ! -f /usr/local/etc/ssh_host_dsa_key ] && ssh-keygen -q -t dsa -f /usr/local/etc/ssh_host_dsa_key -N "" -C ""
exec /usr/local/sbin/sshd $@
Now, when you enable Remote Login from the System Preferences, your sshd will be the one starting.
If it doesn't work the way you expect, use tail in a second terminal window to follow the systemlog:
tail -f /var/log/system.log
This should give you some clues as to what's failing.
The ipfw version of TheHand.sh should also speak aloud when triggered--if it doesn't then the path to it configured may be wrong, or there may be a permissions error. You can also run this script manually, eg:
sudo /usr/local/libexec/TheHand.sh 2.2.2.2 blah blah blah