Automatic blocking of remote IP hosts attacking ssh and other services

Script to record how many times system services are being probed, using configurable pattern matching to recognize failed accesses (such as for "sshd" or "proftpd" or any service), and when a particular IP address exceeds a certain number of failed attempts, that IP address is blocked by using multiple techniques: using /etc/hosts.allow for services that support TCP_WRAPPERS, or by executing ip route commands to setup null-routing for that source host address, or by executing iptables commands to setup packet filtering to drop packets from a source host address. Both IPv4 and IPv6 supported.

Version 2.7.0 and older: Requires python version 2.3, and runs on Unix-like machines only.
2021-March: Version 3.0.0 and newer: Requires Python3: github blockhosts

Script is most suitable for home Linux users, who need to keep ssh/ftp ports or other services open, and need to block the script kiddies. With null-routing or packet filtering, any service can be protected, since those mechanisms block at the IP routing or packet filtering level. In this case, the only requirement is that there should be a way to detect failed accesses in some system log file.

Also available: BlockHosts Forum and BlockHosts FAQ.



I wanted to let you know that I feel that this is an excellent piece of software.

I wanted to share a working /etc/hosts.allow file from FreeBSD to save others some work. The default log file for sshd authentication errors in the /var/log/auth.log

This configuration spaws each time sshd is accessed so there is no need to run CRON to check the logs for activity.

sshd : some_allowed_host : allow

#---- BlockHosts Additions

#bh: logfile: /var/log/auth.log
#bh: offset: 0
#bh: first line:

#---- BlockHosts Additions
sshd : ALL : spawn (/usr/local/bin/ )
sshd : ALL : allow

FreeBSD configuration

I noticed Brad's comment, but when I attempt to install, I get a missing module message.

Script requires modules from python 2.3, may not work with earlier versions.
No module named mx.Tools.NewBuiltins

I currently have Python 2.4.1_1 installed on a FreeBSD 5.4 box

Any help would greatly be appreciated.


Edit script

For a quick fix, edit the file using a text editor:
1: remove the line "import mx.Tools...." from
2: search for "reval(" and change it to "eval("

In the next release, I'll remove this dependency - not really necessary to use reval() for this script.

Alternately, figure out how to install the mx.Tools modules on your box - as one of the other comments mentioned,
in relation to Debian.

Found mx tools

For those installing on FreeBSD you will need to install the following package in addition to Python. py-mx-base from /usr/ports/lang

Attempts set to 6 bad tries, user blocked on 3rd attempt

One thing I noticed was a friend of mine was granted access on his third login attempt (fat finger), when I checked hosts.allow it had entered his IP as a blocked host, and this is set for 6 failed attempts in my blockhosts.cfg file. Any ideas?




To install this script on a stock debian box you will need to fulfill a couple of dependancies:
apt-get install python-egenix-mxtools python2.3-dev

Funny... mine worked without mxtools

A quick browse of the source (0.9) didn't find any reference to mxtools, either. This is a Debian mixed sarge/sid installation.

Nice work, Avinash! I'll let you know if I have any problems.

- Larry.

Ah! CHANGES tells all!

CHANGES file for 0.9 sayeth:

* Removed import of NewBuiltins, use eval() for all .cfg file input,
since eval() has to be used for atleast one of the lines anyway

billr was on an earlier version, as was the *BSD poster

Script to firewall via blockhosts' hosts.deny with iptables

Tweak to suit your needs, I have a cron job that runs every few minutes. Stops ssh abusers quick, since hosts.deny doesnt seem to...


copy below line, put into a script file and chmod +x. May need to tweak it based on your config, I am using Fedora Core 3.... When blockhosts removes the IP from the hosts.deny file, the firewall rules will change to match it, thanks to the FLUSH that happens right off the bat.


iptables -F INPUT

for X in `cat /etc/hosts.deny | tr -d '\t' | grep deny | tr -d ' ' | tr -d 'ALL:' | tr -d 'deny' | grep '[0-9]'`
echo "Blocking:" $X
iptables -A INPUT -s $X -j DROP

iptables -A INPUT -j RH-Firewall-1-INPUT

iptables -L INPUT

How is the iptables script activated?

This is exactly what I've been looking for in a script, but I'm not sure how you would activate this script via Can you comment on what the line within the host.deny would look like or the host.allow if that's where you are running blockhosts from?

Thank you in advance for your reply.

How to plug this hole?

The Skiddies seem to have found a chink in my blockhosts armor. My auth.log (Debian mixed sid/sarge) has many lines like:

Jun 24 15:27:20 ipanema sshd[12899]: Invalid user patrick from
Jun 24 15:27:20 ipanema sshd[12899]: Excess permission or bad ownership on file /var/log/btmp

Blockhosts doesn't react to these attempts. Pardon my ignorance, but how to block these attempts?


More on the empty password attack

OK, so I've figured out that this attack is looking for empty passwords.

And I *think* that (if I only had a brain) I could change the reg-exp to /etc/blockhosts.cfg to catch this. The distributed regexp is:

"SSHD_FAIL": re.compile(r"""sshd\[\d+\]: Failed (?P.*) for (?Pinvalid user |illegal user )?(?P.*?) from (::ffff:)?(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"""),

Unfortunately, my reg-exp fu and py-skills are weak, and the warnings in /etc/blockhosts.cfg are dire!



Fix permissions instead?

Instead of adding a reg-exp, I think it would be better to fix what sshd is complaining about - permissions on /var/log/btmp

Make sure /var/log/btmp has permissions 600, and owned by root.root. Use "ls -l /var/log/btmp" to check current permissions, and to fix, run these commands (as root):
chmod 600 /var/log/btmp
chown root.root /var/log/btmp

Finally, you can easily add a regexp to look for "Invalid user", but it could cause double-counting - since sshd prints both a Invalid user line and a Failed password line. So, if you are looking for Invalid user, it may be necessary to delete the "failed password" regexp.

OK, I'll try that.

OK, I'll try that.

Empty password (are they?) attacks

No more /var/log/btmp complaints, thanks, AC.

Should I just grin and bear the:

Jun 28 15:19:24 ipanema sshd[670]: Invalid user school from
Jun 28 15:19:28 ipanema sshd[678]: Invalid user comments from
Jun 28 15:19:39 ipanema sshd[698]: Invalid user webeditor from
Jun 28 15:19:41 ipanema sshd[703]: Invalid user webservices from
Jun 28 15:19:58 ipanema sshd[723]: Invalid user webservice from
Jun 28 15:20:00 ipanema sshd[727]: Invalid user onlinebooks from
Jun 28 15:20:02 ipanema sshd[732]: Invalid user www from

... attacks?


To make blockhosts working well, here is the /etc/hosts.deny I used:

# hosts.deny This file describes the names of the hosts which are
# *not* allowed to use the local INET services, as decided
# by the '/usr/sbin/tcpd' server.
# Version: @(#)/etc/hosts.deny 1.00 05/28/93
# Author: Fred N. van Kempen

#---- BlockHosts Additions

#---- BlockHosts Additions

sshd:ALL:spawn (/usr/bin/ --verbose >> /var/log/blockhosts.log 2>&1 )&:allow

proftpd:ALL: spawn (/usr/bin/ --verbose >> /var/log/blockhosts.log 2>&1 )&:allow

The order in which those lines must appeared is important.
Take care of that.



it beats running from cron for sure. Thanks v much for the tip!

Not picking up failed attemps


Great script and great idea. I have it installed but I can't seem to get it working properly. SSH access on my machine is limited. No root login access, no password auth as I use public key and only users listed in the AllowGroups are allowed access anyway. This doesn't deter the skiddies out there who keep trying (and failing). All I end up doing is flood pinging their IP until their attempt stops.

Anyway, in my log files I get this sort of thing
Jul 19 06:47:27 parsley sshd[1768]: Invalid user carol from
Jul 19 06:47:28 parsley sshd[1770]: Invalid user cesar from
Jul 19 06:47:29 parsley sshd[1772]: Invalid user clark from
Jul 19 06:47:30 parsley sshd[1774]: Invalid user clinton from
Jul 19 06:47:30 parsley sshd[1776]: Invalid user kayla from
Jul 19 06:47:31 parsley sshd[1778]: Invalid user russ from
Jul 19 06:47:32 parsley sshd[1780]: Invalid user white from
Jul 19 06:47:33 parsley sshd[1782]: Invalid user danny from
Jul 19 06:47:34 parsley sshd[1784]: Invalid user filip from
Jul 19 06:47:35 parsley sshd[1786]: Invalid user stephanie from
Jul 19 06:58:23 parsley sshd[2821]: User root from not allowed because none of user's groups are listed in AllowGroups
Jul 19 06:58:24 parsley sshd[2823]: User root from not allowed because none of user's groups are listed in AllowGroups
Jul 19 06:58:25 parsley sshd[2825]: User root from not allowed because none of user's groups are listed in AllowGroups
Jul 19 06:58:26 parsley sshd[2827]: User root from not allowed because none of user's groups are listed in AllowGroups
Jul 19 06:58:28 parsley sshd[2829]: User root from not allowed because none of user's groups are listed in AllowGroups
Jul 19 06:58:29 parsley sshd[2831]: User root from not allowed because none of user's groups are listed in AllowGroups

When I run blockhosts it says this

blockhosts 1.0.0 started: 2005-07-19 11:32:13
... will discard all host entries older than 2005-06-19 11:32
... load blockfile: loading from file: /etc/hosts.deny
... found both markers, count of hosts being watched: 0
... securelog, loading from file: /var/log/auth.log 151198
... updates: counts: hosts to block: 0; hosts being watched: 0

I checked the hosts.deny file and surely enough, nothing is added. Any idea whats going on? I am assuming the regex's aren't catching whats in my auth.log file but my knowledge of regex's is very basic so I'm not entirely sure whats happening here. My server runs Debian 2.4.18-bf2.4.

Cheers :)

Which version?

Do you know which version of sshd you are using?
Try running:
sshd -?
It may print out the version number.

My script was based on OpenSSH, version 3.9. That version always prints a "Failed login" line for every failed attempt, in addition to the lines you list above.

Looks like there are sshd versions out there that only print above lines.

I don't know yet how best to handle this - these are the pitfalls of depending on log parsing to determine actions!

But if you feel like editing your file, you can take care of the problems by adding these two lines after the current line that has the word "SSHD-Illegal" in it - around line 148 (note: indentation is important - follow the same indentation as line 148, there must be 8 spaces - no tabs before each line below!):

"SSHD-Invalid": re.compile(r"""sshd\[\d+\]: Invalid user (?P<user>.*?) from (::ffff:)?(?P<host>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"""),
"SSHD-NotAllowed": re.compile(r"""sshd\[\d+\]: User (?P<user>.*?) from (::ffff:)?(?P<host>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) not allowed"""),

Note that there are two single lines above (ignore wrapping), and each line should have 8 spaces before it when you add it to Even minor issues with the lines will cause python to barf...

Open SSH version

I'm running this version: OpenSSH_4.1p1 Debian-6, OpenSSL 0.9.7g 11 Apr 2005

I know that log parsing is a painful process as I have had to write programs that do it myself :) Your script is a great tool in trying to combat these talentless idiots that plague the net and clog up my servers.

I will try your suggestion when I get home. Many thanks. I'll let you know how I get on. I'll have a more indepth read of the script and see if I can come up with anything. It's doubtful, but at least I can try.

It's now working!

Thanks for your prompt reply. I added the regex lines to the but it didn't do anything. I forgot that I have used the config file in /etc. I added the same lines into the config file, removed the offsets from the hosts.deny file and manually ran the script. Now I have 2 lines added to my hosts.deny. Thank you very much.

At least now I can check my hosts.deny file every now and then and see whats happening. When I get any skiddies from Korea, the Balkan states or Russia, I can at least just move the line to deny their entire IP block.

At least with this I can do something active against them until port knocking gets taken up and supported by the more common apps.

Thanks again :)

Didn't work for me :(

blockhosts 1.0.0
OpenSSH_4.1p1 Debian-6

I tried uncommenting and adding those regexps to /etc/blockhosts.cfg. Didn't work.

Reverted /etc/blockhosts.cfg, added them to Also didn't work.

Are there additional steps that are assumed to be known here? Like a re-install or something?

Try this .cfg file

For all those who don't see a "Failed password..." log line but only see a "Invalid user ..." line in the sshd logs, here's a working .cfg file that I've tested with the log example given above.

To use, replace your /etc/blockhosts.cfg with this file, merging in any changes you made locally. Then the next run of will pick up these changes.

[Oct 2005: Better approach - update to the latest blockhosts - version 1.0.3 or later, it includes these rules.
See details in the CHANGES file, or read about it in the comment to this forum posting: Not picking up failed attempts ]

[Old - dont use, here for archival purpose only:] blockhosts-1.cfg - config file with SSHD-Invalid and SSHD-NotAllowed rules added

This config file contains two new rules in the ALL_REGEXS section.
Note that this may cause double counting of some IP addresses, in some sshd installs. Still, better to double count than to ignore an abusive IP address. Given the double-counting, this change will not be included in the main package, so if anyone knows for sure why some sshd installs do not print the "Failed password..." line, or knows what line to look for, send me email. Am looking for a line that is printed once only for each failed attempt.


Here is my version:
Oct 30 12:19:56 localhost sshd[4742]: Illegal user test from ::ffff:

I added the following rule to my block.cfg:
"SSHD-Illegal": re.compile(r"""sshd\[(?P\d+)\]: Illegal user (?P.*?) from (::ffff

This was just a copy of the one that was marked SSHD-Invalid. Make sure you uncomment the# in front of the All_REGEX line and also at the end of the block where you see #}. Below is the error line in my auth.log:
Nov 5 04:57:19 localhost sshd[11166]: Illegal user paul from ::ffff:

Hope this helps you debians out there hehe :)

Unexpected behaviour

First let me say, excellent script you have here.

I altered my blockhosts.cfg file to add a search for the "not in AllowUsers" string. After re-running I expected to see the IP address from the previous nights 2,817 root break-in attempts added to the deny list. It was not. After fiddling around the RegEx with no betters results, I got frustrated and removed everything from between the "BlockHosts Additions" lines in hosts.allow. After re-running, the entries I expected were correctly added.

I have not read the code, but does the script only parse new entries from the log? This may just be a misunderstanding on my part, but I had assumed that by adding the new RegEx rule and re-running the script, the entire log would be checked and appropriate matches added to the deny list.

For further reference - if a new rule is added to the config, should the existing results be purged from the hosts file and the script re-run? Thanks.

BlockHosts version: 1.0.0-1
openssh version: 4.1p1-3.1
Python version: 2.4.1-2

Ah, bad form to reply to

Ah, bad form to reply to oneself, but I see now from the comment above that there is an offsets variable in the hosts file. Apparently this is what I missed.

Use forum for how-to questions, or for posting tips

forums/blockhosts now available -- use the forum to ask how-to questions, or to post tips on using blockhosts.