Securing your server with DenyHosts

This is an old post. It may contain broken links and outdated information.

Running any kind of server at all is a risk, because the internet is a bad place full of bad people who like to destroy things for fun (and if you don't believe me, read this). It becomes a matter of risk management—you have to expose certain things, like TCP ports 80 and maybe 443, for your web server to be reachable; you also probably need to expose at least one management port somewhere so that your server can be poked and prodded should things go wrong with it.

This usually means exposing port 22 for ssh if you're on some kind of Unix-ish operating system like we are here at the Bigdinosaur.org compound. Blindly exposing your ssh port is not without peril, but there are several things you can do to manage the risk—namely, moving the ssh daemon onto a different port; controlling which local accounts are allowed to log on via ssh; and most importantly, installing and configuring something like DenyHosts, a TCP wrapper designed to help keep undesirables from being allowed to log on at all.

Changing sshd's port

Changing the ssh port is the easiest thing you can do, though its efficacy is arguable. On one hand, this will avoid your being noticed by potential attackers randomly wardialing port 22 on a huge range of IP addresses; on the other hand, the ssh service fingerpring will be blindingly obvious to even a casual port scan, no matter where you're running it. Still, it's easy to implement and it certainly doesn't make anything less secure.

There are two ways to change the port on which sshd is advertised to the world. If you're behind a stateful firewall with NAT, by far the simplest is to use a DNAT rule to forward an arbitrary high port on your public address to port 22 on your web server. Exactly how to do this varies depending on what firewall you're using, but if you know what "DNAT" is then you likely already know how to set up the rule.

The other way is to actually change the ssh daemon's listening port, which on Ubuntu or any other Debian-based distro (and on most GNU/Linux distros in general) can be done by editing/etc/ssh/sshd_config. Locate the following section in that file:

# What ports, IPs and protocols we listen for
Port 22  

Change 22 to a high number—I'd suggest something higher than 30000 but below the maximum 65535. Restart ssh when you're done.

Changing ssh's AllowUsers

The GNU/Linux ssh daemon can be told to only allow certain accounts to authenticate via ssh, and unlike the maybe-effective-but-maybe-not first step above, this step is something everyone should be doing. The most obvious thing to do here is make sure root isn't allowed to log on remotely, but it's not a bad idea to ensure that only accounts that need to allow remote logon are allowed to do so.

Again, open /etc/ssh/sshd_config for editing and search for AllowUsers. If there is no AllowUsersdirective in the file, head to the bottom and add it yourself:

# AllowUsers controls which users are allowed to log on via ssh
# Include only accounts that need remote log on privileges!
AllowUsers john mary fred  

In the above example, only the accounts johnmary, and fred can log on via ssh; logon attempts by the user bill would be rejected.

Installing and configuring DenyHosts

However, it's never wise to expose ssh to the Internet without breaking out the big guns, and that means something like DenyHosts. DenyHosts is a handy application which not only stops repeated bad logons to your server, but which also communicates with a central database to block known malcontents from logging onto your server before they even try.

DenyHosts is available from the Ubuntu or Debian repositories with a quick sudo aptitude install denyhosts, but the version available in the repositories as of this blog post is out of date and is missing a critical bugfix. To install the latest version, run these two commands:

$ wget http://mirror.pnl.gov/ubuntu//pool/universe/d/denyhosts/denyhosts_2.6-10_all.deb
$ sudo dpkg -i denyhosts_2.6-10_all.deb

This will take care of installing DenyHosts and setting it up to run at startup. DenyHosts keeps track of bad ssh logon attempts, blocks IP addresses with too many bad attempts, and can optionally be configured to both download lists of known-naughty IP addresses from the DenyHosts central database and also to upload IP addresses which have tried and failed to log onto your server to the central database (so others can download them and automatically block them). All of DenyHosts's configuration is kept in /etc/denyhosts.conf, so that's what we need to edit. Here are the DenyHosts settings I've modified away from default, along with why:

# PURGE_DENY: Block list entries which are older than this parameter will be
# removed from the block list when DenyHosts runs its purge cycle. This is
# automatically done when DenyHosts runs in daemon mode.
PURGE_DENY = 30d

# PURGE_THRESHOLD: Maximum number of times an IP address will be purged from the
# block list. After this, they stay on it no matter how long they've been there.
PURGE_THRESHOLD = 2

# DENY_THRESHOLD_INVALID: If a host tries to log on with an account that doesn't
# exist, they'll be added to the block list after this many failed attempts.
DENY_THRESHOLD_INVALID = 1

# DENY_THRESHOLD_VALID: If a host tries to log on with an account that *does*
# exist, they'll be added to the block list after this many failed attempts.
# Setting this too low could really screw up your remote access if you fat-finger
# your password!
DENY_THRESHOLD_VALID = 3

# DENY_THRESHOLD_ROOT: If a host tries to log on with the root account, they'll
# be added to the block list after this many failed attempts.
DENY_THRESHOLD_ROOT = 1

# SYSLOG_REPORT: If set to yes, DenyHosts will note additions to the block list
# in your syslog file
SYSLOG_REPORTING = YES

# AGE_RESET_VALID: Time interval after which hosts which tried to log on using
# real accounts are removed from the block list.
AGE_RESET_VALID = 1h

# RESET_ON_SUCCESS: Clears invalid logon attempt counter for a given account
# if the account successfully logs on. Set this to yes because you'll probably
# hurt yourself more by having it set to no.
RESET_ON_SUCCESS = yes

# SYNC_UPLOAD: Allows or disallows DenyHosts from sending a copy of its block
# list to the central DenyHosts database for others to download.
SYNC_UPLOAD = yes

# SYNC_DOWNLOAD: Allows or disallows DenyHosts from downloading a big list of
# potentially naughty hosts from the central DenyHosts database, and then 
# adding them to your own block list.
SYNC_DOWNLOAD = YES  

Leave all other settings to default, then restart the service with sudo /etc/init.d/denyhosts restart. DenyHosts keys off of entries in your system's main auth.log file, and so keeping an eye on that file will reveal what DenyHosts is doing. Here's a snippet from the Bigdino web server:

Feb  6 06:28:20 webserver sshd[8738]: User root from geeks.ne.jp not allowed because not listed in AllowUsers  
Feb  6 06:28:20 webserver sshd[8738]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=geeks.ne.jp  user=root  
Feb  6 06:28:23 webserver sshd[8738]: Failed password for invalid user root from 114.179.254.227 port 39559 ssh2  
Feb  6 06:28:23 webserver sshd[8745]: refused connect from geeks.ne.jp (114.179.254.227)  
Feb  6 07:09:50 webserver sshd[8895]: refused connect from dhcp83.cs.columbia.edu (128.59.22.22)  
Feb  6 07:26:55 webserver sshd[8902]: refused connect from 223.25.222.104 (223.25.222.104)  
Feb  6 16:35:42 webserver sshd[9265]: Did not receive identification string from 140.114.15.172  
Feb  6 23:33:26 webserver sshd[9782]: refused connect from 195.151.207.139 (195.151.207.139)  
Feb  6 23:56:35 webserver sshd[9793]: refused connect from 195.151.207.139 (195.151.207.139)  
Feb  7 11:23:53 webserver sshd[10695]: refused connect from static-179-44-210-31.sadecehosting.net (31.210.44.179)  
Feb  7 22:15:08 webserver sshd[11842]: Did not receive identification string from 67.215.180.2  
Feb  7 22:21:21 webserver sshd[11869]: User root from router2.grupotba.com not allowed because not listed in AllowUsers  
Feb  7 22:21:21 webserver sshd[11869]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=router2.grupotba.com  user=root  
Feb  7 22:21:24 webserver sshd[11869]: Failed password for invalid user root from 67.215.180.2 port 61537 ssh2  
Feb  7 22:21:24 webserver sshd[11876]: refused connect from router2.grupotba.com (67.215.180.2)  
Feb  8 00:21:14 webserver sshd[11934]: refused connect from 124.124.212.172 (124.124.212.172)  
Feb  8 00:41:35 webserver sshd[11978]: refused connect from 124.124.212.172 (124.124.212.172)  
Feb  8 02:32:10 webserver sshd[12081]: refused connect from 95.173.166.182 (95.173.166.182)  
Feb  8 03:00:27 webserver sshd[12092]: refused connect from 95.173.166.182 (95.173.166.182)  
Feb  8 07:40:12 webserver sshd[12541]: refused connect from www3043u.sakura.ne.jp (59.106.176.81)  
Feb  8 07:41:49 webserver sshd[12542]: refused connect from www3043u.sakura.ne.jp (59.106.176.81)  
Feb  8 07:42:03 webserver sshd[12543]: refused connect from www3043u.sakura.ne.jp (59.106.176.81)  
Feb  8 07:42:18 webserver sshd[12544]: refused connect from www3043u.sakura.ne.jp (59.106.176.81)  
Feb  8 07:42:34 webserver sshd[12545]: refused connect from www3043u.sakura.ne.jp (59.106.176.81)  
Feb  8 07:42:50 webserver sshd[12546]: refused connect from www3043u.sakura.ne.jp (59.106.176.81)  
Feb  8 07:43:06 webserver sshd[12547]: refused connect from www3043u.sakura.ne.jp (59.106.176.81)  
Feb  8 07:43:22 webserver sshd[12548]: refused connect from www3043u.sakura.ne.jp (59.106.176.81)  
Feb  8 07:43:38 webserver sshd[12549]: refused connect from www3043u.sakura.ne.jp (59.106.176.81)  
Feb  8 07:43:54 webserver sshd[12551]: refused connect from www3043u.sakura.ne.jp (59.106.176.81)  
Feb  8 07:44:11 webserver sshd[12552]: refused connect from www3043u.sakura.ne.jp (59.106.176.81)  
Feb  8 07:44:27 webserver sshd[12553]: refused connect from www3043u.sakura.ne.jp (59.106.176.81)  

You can see a mix of outright denials and also a couple of root attempts (after which the host is added to the block list). The authentication log should be audited once a day or so in order to keep an eye on system-wide activity, so reviewing this should be something you're doing regularly.

If a host is ever added to the block list by mistake, it's a snap to remove it—the actual block list file is/etc/hosts.deny and can be edited as needed. You can also manually add hosts you want to block in to the same file.

Installing DenyHosts is only one large number of things you can do to help secure your web server, and every little bit helps. It's not necessary to create a completely air-tight and secure server because down that road lies madness, as you chase down every single tiny possible vulnerability. Rather, you only need to make your web server more secure than others. Remember the adage about the dragon and the hobbit: if you ever find yourself being pursued by a hungry dragon while in the company of a hobbit, remember that you don't have to outrun the dragon—you just have to outrun the hobbit.