For a long time now I've had a lot of problems with brute force attacks against sshd and proftpd - attacks where a host will attempt to login with a dictionary of common usernames and passwords, trying each one until they find a combination that works. Apart from being a security issue, this uses up a lot of bandwidth so it's worth taking some measures to block these kind of attacks.
Both sshd and ftpd services have their own individual means for blocking individual connections, but unfortunately neither have an inbuilt method for detecting brute force attacks - counting how many failed login attempts are made from each individual IP address and then blocking that IP address if the number of failed login attempts is more than a certain number. This is where a 3rd party utility is required.
There are a few utilities that can mitigate brute force attacks on services. For a while now I've used
DenyHosts successfully to block sshd brute force attacks. DenyHosts works by constantly monitoring sshd logfiles and keeping track of how many failed logins have occured per IP address over time. If the number of failed logins reaches a certain threshold, DenyHosts adds an entry in /etc/hosts.allow that effectively blocks the IP address, stopping that host from connecting to the sshd service any more.
DenyHosts is great, but unfortunately it's aimed only at blocking sshd brute force attacks and I need to protect the ftpd service as well as just sshd - and in future maybe adapt to block other services. With this in mind I decided to move to using a very similar script called
BlockHosts (the documentation for BlockHosts actually mentions that it was inspired by DenyHosts). BlockHosts can scan a list of service logfiles in one go instead of just a single logfile as with DenyHosts, so is ideal for monitoring a number of different services for brute force attacks.
The following describes how to install and configure BlockHosts on FreeBSD so it's executed every time the sshd or proftpd services are accessed using TCP_WRAPPERS - ie modifying /etc/hosts.allow so the blockhosts script is run each time sshd or proftpd are accessed. The BlockHosts script will then check if this current connection attempt is part of a brute force attack and if so, add a blocking rule to /etc/hosts.allow to deny further access.
Installation of BlockHosts
- Download blockhosts from the download page, extract the distribution (note please check the download link for the latest version, the version below was latest at time of writing):
CODE:
root@users /home/munk/bin/python/blockhosts# wget http://www.aczoom.com/tools/blockhosts/BlockHosts-1.0.5.tar.gz
root@users /home/munk/bin/python/blockhosts# tar zxvf BlockHosts-1.0.5.tar.gz
BlockHosts-1.0.5/
BlockHosts-1.0.5/Makefile
...
- Change to BlockHosts directory:
CODE:
root@users /home/munk/bin/python/blockhosts# cd BlockHosts-1.0.5
- Edit and save blockhosts.py to read:
CODE:
CONFIG_FILE = "/usr/local/etc/blockhosts.cfg"
...
"LOGFILES": ( "/var/log/auth.log", ),
Note: may seem a bit odd editing the blockhosts.py script before it's installed - the reason for this is that the installation locations used by setup.py below are taken from blockhosts.py, so by modifying blockhosts.py like this we get the config file installed into /usr/local/etc/ (FreeBSD default for 3rd party software) instead of into /etc (default for linux 3rd party software).
- Install blockhosts:
CODE:
root@users /home/munk/bin/python/blockhosts/BlockHosts-1.0.5# python setup.py -v install
running install
running build
running build_scripts
creating build
creating build/scripts-2.4
copying and adjusting blockhosts.py -> build/scripts-2.4
changing mode of build/scripts-2.4/blockhosts.py from 644 to 755
running install_scripts
copying build/scripts-2.4/blockhosts.py -> /usr/local/bin
changing mode of /usr/local/bin/blockhosts.py to 755
running install_data
copying blockhosts.cfg -> /usr/local/etc
This installs the blockhosts.py script into /usr/local/bin and the config file blockhosts.cfg into /usr/local/etc. Make sure to run 'rehash' to reread the binary paths again so blockhosts.py will run from anywhere:
CODE:
root@users /home/munk/bin/python/blockhosts/BlockHosts-1.0.5# rehash
- Edit and save the /usr/local/etc/blockhosts.cfg file so it reads:
CODE:
LOGFILES = [ "/var/log/auth.log", "/var/log/ftp.log" ]
Important:
Add the logfiles you want blockhosts to monitor for brute force attacks here. /var/log/auth.log is standard for sshd, /var/log/ftp.log is maybe not standard for all ftpd, this is just what I have setup here.
At this point it's best to read through the documentation for blockhosts completely - the README, INSTALL and the blockhosts.py script itself. The following section is pretty much copy/pasted from what's mentioned in there.
- Edit and save /etc/hosts.allow to include the section that blockhosts.py will modify. Make sure you allow your own IP blocks first and any trusted IPs so they don't get blocked accidentally:
GOTCHA LOOKOUT!
One gotcha to watch out for in this is the line:
CODE:
ALL : ALL : allow
You MUST remove this line - replace it with your IP block instead so you don't get locked out from your own address range. If this line isn't removed/commented out, anything below it just isn't read/executed and blockhosts won't work.
This is how my /etc/hosts.allow looks:
CODE:
#######################################################################
# blockhosts
#######################################################################
# ----
# see "man 5 hosts_access" for details of the format of IP addresses,
#services, allow/deny options. Also see "man hosts_options"
#order of lines in this file is important, first matched IP address line
#is rule applied by hosts_access
#
# permanent whitelist addresses - these should always be allowed access
ALL : 213.152.51.192/255.255.255.248 : allow
# ALL: 127.0.0.1 : allow
# ALL: 192.168.0. : allow
# permanent blacklist addresses - these should always be denied access
# ALL: 10. : deny
# ALL: 192. : deny
# ALL: 172. : deny
# ----------------------------------------
# next section is the blockhosts section - it will add/delete entries in
# between the two marker lines (#---- BlockHosts Additions)
#---- BlockHosts Additions
#---- BlockHosts Additions
# ----------------------------------------
# finally, the command to execute the blockhosts script, based on
# connection to particular service or services, for example, for
# sshd and proftpd - if using vsftpd, pure-ftpd, be sure to use those
# words instead:
sshd, proftpd: ALL: spawn (/usr/local/bin/blockhosts.py --verbose --echo "%c-%s" >> /var/log/blockhosts.log 2>&1 )& : allow
# remove: >> /var/log/blockhosts.log 2>&1 if logging to
# blockhosts.log is not needed - it will still log to syslog (minimally)
# see examples below
# --
# See "man hosts.allow" for info on %c and %s identifiers
#----
# for non-verbose, with identification, to syslog only (/var/log/messages):
#sshd, proftpd, in.proftpd: ALL: spawn /usr/bin/blockhosts.py --echo "%c-%s" & : allow
#----
# minimal logging, to syslog (usually goes to /var/log/messages):
#sshd, proftpd, in.proftpd: ALL: spawn /usr/bin/blockhosts.py & : allow
#----
# To test hosts.allow, and to find out exact names of SSH/FTP services,
# add this line to the beginning of hosts.allow, use ssh/ftp to connect
# to your server, and then look at the log (/var/log/messages or
# blockhosts.log) to see the name of the invoked service.
# IMPORTANT: after your test is done, remove this line from hosts.allow!
# Otherwise everyone will always have access.
#ALL : ALL: spawn (/usr/bin/blockhosts.py --verbose --echo "%c-%s" >> /var/log/blockhosts.log 2>&1 )& : allow
#######################################################################
# blockhosts
#######################################################################
Important Note for ProFTPD users:
The following sections describes the configuration needed when using proftpd via inetd. If you are using ProFTPD in standalone mode, you need to use the proftpd mod_wrap/mod_wrap_file functionality to have proftpd read and honour the TCP_WRAPPERS//etc/hosts.allow file(s) when denying/allowing hosts. Additionally you need to specifiy the configure flag --enable-wrapper-options when building proftpd. For a heavily used server, this might be worth doing but personally I don't get that many connections that I need to worry about inetd being overloaded so I can just go down the (easier to configure for blockhosts) inetd path.
- Ensure proftpd is configured to run correctly via inetd.
Edit and save /usr/local/etc/proftpd.conf to read:
CODE:
ServerType inetd
Important: remember to delete or rename /usr/local/etc/rc.d/proftpd.sh so it's not run at boot time - the proftpd daemon doesn't need to be started at boot if you're using inetd, inetd handles all the proftpd connections, see below:
Edit and save /etc/inetd.conf to read:
CODE:
ftp stream tcp nowait root /usr/local/sbin/in.proftpd proftpd
then restart inetd:
CODE:
root@users /usr/local/etc# kill -HUP `cat /var/run/inetd.pid `
This forces inetd to restart, rereading the config file changes made to /etc/inetd.conf. ftp connections will now be handled by proftpd via inetd.
We're now ready to run blockhosts.py for the first time. BlockHosts will parse each logfile mentioned in blockhosts.cfg and check for any brute force attacks and if it finds any, blocks will be added to the /etc/hosts.allow file.
Note: This initial check does not take into account the period over which failed logins took place, so any IP that has more than the default 7 failed login entries will look like a brute force attacker. However, the ban BlockHosts adds will only last for the default 12 hours so this shouldn't cause a huge issue - just be aware of this and check the IPs that are added on the first run.
For the very first time it's a good idea to try a 'dry run' just to see what blockhosts finds and what it'd do, without actually doing anything to the /etc/hosts.allow file. To do this, run blockhosts with the '--dry-run' flag:
CODE:
root@users /usr/local/etc# /usr/local/bin/blockhosts.py --verbose --dry-run
blockhosts 1.0.5 started: 2006-12-30 14:15:30
... will discard all host entries older than 2006-12-30 02:15
... load blockfile: /etc/hosts.allow
... found both markers, count of hosts being watched: 0
Warning: no offset found, will read from beginning in logfile: /var/log/auth.log
... securelog, loading file, offset: /var/log/auth.log 0
Warning: no offset found, will read from beginning in logfile: /var/log/ftp.log
... securelog, loading file, offset: /var/log/ftp.log 0
... updates: counts: hosts to block: 9; hosts being watched: 21
#---- BlockHosts Additions
ALL: 203.88.192.225 : deny
ALL: 200.71.192.7 : deny
ALL: 212.227.81.146 : deny
ALL: 218.25.62.75 : deny
ALL: 200.46.108.164 : deny
ALL: 201.57.163.2 : deny
ALL: 205.129.191.11 : deny
ALL: 200.68.51.91 : deny
ALL: 82.38.68.217 : deny
#bh: ip: 85.184.10.200 : 1 : 2006-12-30-14-15
#bh: ip: 84.158.231.209 : 1 : 2006-12-30-14-15
#bh: ip: 82.38.68.217 : 11 : 2006-12-30-14-15
#bh: ip: 82.153.28.16 : 2 : 2006-12-30-14-15
#bh: ip: 67.113.225.66 : 1 : 2006-12-30-14-15
#bh: ip: 59.108.34.228 : 2 : 2006-12-30-14-15
#bh: ip: 222.68.192.132 : 2 : 2006-12-30-14-15
#bh: ip: 218.25.62.75 : 20 : 2006-12-30-14-15
#bh: ip: 217.83.162.157 : 1 : 2006-12-30-14-15
#bh: ip: 212.227.81.146 : 29499 : 2006-12-30-14-15
#bh: ip: 210.1.132.178 : 4 : 2006-12-30-14-15
#bh: ip: 205.129.191.11 : 20 : 2006-12-30-14-15
#bh: ip: 204.141.87.14 : 3 : 2006-12-30-14-15
#bh: ip: 203.88.192.225 : 448 : 2006-12-30-14-15
#bh: ip: 202.108.40.109 : 1 : 2006-12-30-14-15
#bh: ip: 201.57.163.2 : 2867 : 2006-12-30-14-15
#bh: ip: 200.71.192.7 : 761 : 2006-12-30-14-15
#bh: ip: 200.68.51.91 : 10 : 2006-12-30-14-15
#bh: ip: 200.46.108.164 : 170 : 2006-12-30-14-15
#bh: ip: 200.105.255.90 : 7 : 2006-12-30-14-15
#bh: ip: 152.104.125.14 : 3 : 2006-12-30-14-15
From this you can see nicely what blockhosts makes of the service logfiles and the addresses that have tried to connect unsuccessfully. On my host, as you can see above, there are a few that are obviously dodgy (I would only expect a max of maybe 8 connections per ip per month, so clearly 29,499 connections is just
wrong!).
Once you're happy that the output is correct, run blockhosts again without the '--dry-run' flag and the /etc/hosts.allow file will be modified. Also from now on the logfiles will only be read from the last recorded offset which saves a lot of time if your logfiles are very big.
Big thanks to the BlockHosts author Avinash Chopde !