[See Adding malicious IPs in DNSBLs to iptables automatically for a new and improved version of this that uses iptables instead of hosts.deny, which is necessary since tcpwrappers is mostly deprecated at this point.]
I run my own mail server, which means that there are hackers trying 24×7 to break into the server by connecting to my SMTP or IMAP server and trying to guess the username and password of one of the accounts on it. They send username / password guesses to the server over and over, essentially continuously, hoping that one of them will eventually succeed.
As far as I know, thus far no one has managed to break into my server. However, if they were to able to keep trying 24×7 essentially forever, then eventually they would succeed, because the password-strength requirements I impose on my users aren’t so draconian, so it’s likely that some of the passwords aren’t complex enough to be invulnerable to a brute-force attack.
Cybersecurity is an arms race, and this tiny corner of cybersecurity is no exception. In the “good old days,” I was able to protect my server against brute-force attacks of this sort by using fail2ban. Alas, it turns out that nowadays that is no longer good enough, for three reasons:
- Fail2ban works by looking for repeated login failures from a single IP address. Alas, the hackers are wise to that trick, and they’re no longer sending login requests from a single address. Instead, they’re using botnets to generate the requests, which means they’re spread out over hundreds or even thousands of IP addresses.
- Fail2ban’s model relies on each failed login attempt generating a separate log message with the IP address in it. Unfortunately, some programs, such as sendmail, will allow numerous login attempts over a single connection before generating a log message with the IP address in it.
- Some programs, again such as sendmail, generate log messages which might indicate a malicious login attempt and then again might not, so it’s risky to allow fail2ban to ban addresses based on those messages.
In short, fail2ban alone will not protect a mail server from brute-force password guessing attacks. What is a conscientious sysadmin to do?
Well, what I decided to do is to write a little daemon which monitors my IMAP and sendmail logs looking for potentially malicious IP addresses, looks up those IP addresses in the XBL and SBL Spamhaus Blackhole lists (DNSBLs), and adds any IP addresses that are on the DNSBLs to my /etc/hosts.deny file.
This is by no means a perfect solution. A sufficiently large botnet could still make thousands of password guesses by spreading them out across all of its zombies. Nevertheless, this approach drastically reduces the number of password guesses a hacker can make, and therefore it drastically reduces the odds that the they will be successful.
This approach also solves the problem of ambiguously malicious log messages; while such a log message by itself might not be definitive, the log message plus a match against one of the DNSBLs is enough to justify banning an address.
Another big advantage of this approach is that since a suspicious log message plus a DNSBL match is so much more likely to indicate hacking than just several log messages in a row, it’s safe for my script to ban IP addresses for much longer than it’s safe for fail2ban to do so. By default, my script bans IP addresses for an entire day.
I would have thought that someone else would have already figured out how to integrate DNSBLs into tcp_wrappers or fail2ban, but I couldn’t find a ready-made solution when I went digging around the internet for one, so I wrote my own. If you know of some other tool that’s already out there that does this, please let me know so that I don’t have to maintain my own. 😉
In the meantime, I’ve published my script for the benefit of anyone else who might find it useful. Share and enjoy!
P.S. I write nearly all my code nowadays in Python, but I’m an old-time Perl hacker, and there are still a few tasks which seem to hit Perl’s sweet spot more than Python’s. This was one of them, so voilà, this script is written in Perl.