One of the most common attack vectors against servers is bruteforce login attempts. This is where attackers attempt to access your server, by trying endless combinations of usernames and passwords. So how do you defend yourself against this kind of attack?
If you’re a web application developer, the most common response might be to refactor some aspect of your application. There’s nothing wrong with this approach, but it’s likely not the right solution. This is because there are specifically-designed, open source applications for defending against this very type of attack.
In today’s post, I’m going to introduce you to one, and show you how to use it; it’s called Fail2Ban. In the setup we’re going to walk through, you’ll see how to defend against brute force SSH logins.
Originally developed by Cyril Jaquier back in 2004, Fail2Ban is:
An intrusion prevention software framework that protects computer servers from brute-force attacks.
More specifically, quoting from the Fail2Ban website:
Fail2ban scans log files (e.g.,
/var/log/apache/error_log) and bans IPs that show malicious activity, such as too many password failures, and seeking for exploits, etc. Generally, Fail2Ban is then used to update firewall rules to reject the IP addresses for a specified amount of time, although any other arbitrary action (e.g., sending an email) could also be configured.
Now you might be asking, if your website’s the target of malicious activity, why wouldn’t you just ban the host permanently? Good question. And on the surface of it, that might sound like the logical thing to do.
But what if the seemingly malicious activity wasn’t malicious. What if it was a genuine user — perhaps even you — who fat-fingered the keyboard, whether because of a lack of sleep, stress, or some other valid cause? What if the user just plain forgot their password?
Would you want them to be permanently locked out of their own server, with no way of logging back in? I’d suggest the answer would be a resounding no! That’s why the ban isn’t permanent, at least not by default.
What’s more, assuming that the activity is genuinely malicious, ban’s don’t need to be permanent. Often, all you need to do is to discourage attackers from attacking you, not block them altogether. Discouraging them normally makes you a target not worth attacking, because it requires too much effort, or too much time.
To be fair, this isn’t always the case, as you may be a particularly inviting target. But if you can make it a royal PITA to attack you, the majority of attackers are likely to just go elsewhere and find lower-hanging fruit. That’s why Fail2Ban is an excellent tool for helping to protect your application from brute force login attempts.
For the purposes of this post, I’ll assume that you’re using a Ubuntu-based Linux server to host your website. The server that I used as the foundation for the information in this post was Ubuntu 14.04.5 LTS.
Yes, it’s a little old now, but fundamentally you configure Fail2Ban the same way and use the same command-line tools in later releases.
If you’re using a different distribution, adjust the commands and package names to suit. To install Fail2Ban, first update your Apt cache, and then install Fail2Ban as shown in the following example.
sudo apt-get update && sudo apt-get install -y fail2ban
When installed, you’ll find Fail2Ban’s configuration files in
If you run
ls -l on that directory, you should see output similar to the following:
drwxr-xr-x 2 root root 4096 Jan 24 16:09 action.d -rw-r--r-- 1 root root 2328 Aug 1 2015 fail2ban.conf drwxr-xr-x 2 root root 4096 Aug 2 2015 fail2ban.d drwxr-xr-x 3 root root 4096 Jan 24 16:09 filter.d -rw-r--r-- 1 root root 18562 Aug 1 2015 jail.conf drwxr-xr-x 2 root root 4096 Jan 24 16:09 jail.d
Fail2Ban’s configuration is split, primarily, across two key files; these are
fail2ban.conf is Fail2Ban’s broader configuration file, where you can configure such settings as:
jail.conf is where you configure such options as:
When setting up Fail2Ban, you should never alter jail.conf, as it’s part of the core distribution and may be overwritten when you upgrade the package. Instead, copy it to a new file called jail.local, and make changes there instead.
Before we dive into configuring Fail2Ban, we need to cover one key concept, and that’s a jail. You’ll hear the term as you dig through the Fail2Ban documentation and configuration files.
The term is pretty apt as, in a figurative sense, we want to jail offenders that try to maliciously access our servers.
A jail is a configuration grouping or bucket.
These can be configured both globally, in jail.conf, and in individual jail configuration files, stored in
Local configuration directives take precedence over global configuration directives.
Regardless of where you store the configuration, in them you store all the details for a specific service that you want Fail2Ban to defend. They’re composed of two parts, the core configuration and a filter.
Here’s an example of the core configuration for ssh.
What this does is check for malicious attempts to access the server on the default SSH port (22).
It does this by parsing
/var/log/auth.log looking for the pattern defined in the
sshd filter config file.
This filter file is stored in the filter.d directory, named:
Finally, if the pattern occurs six times, then it’s considered to be malicious activity.
[ssh] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 6
There’s more to a filter file than I’m going to cover here.
What I want to show you is the key configuration item:
As Fail2Ban parses log files, it uses regular expressions to do so as efficiently as possible.
failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|error) for .* from <HOST>( via \S+)?\s*$ ^%(__prefix_line)s(?:error: PAM: )?User not known to the underlying authentication module for .* from <HOST>\s*$ ^%(__prefix_line)sFailed \S+ for .*? from <HOST>(?: port \d*)?(?: ssh\d*)?(: (ruser .*|(\S+ ID \S+ \(serial \d+\) CA )?\S+ %(__md5hex)s(, client user ".*", client host ".*")?))?\s*$ ^%(__prefix_line)sROOT LOGIN REFUSED.* FROM <HOST>\s*$ ^%(__prefix_line)s[iI](?:llegal|nvalid) user .* from <HOST>\s*$ ^%(__prefix_line)sUser .+ from <HOST> not allowed because not listed in AllowUsers\s*$ ^%(__prefix_line)sUser .+ from <HOST> not allowed because listed in DenyUsers\s*$ ^%(__prefix_line)sUser .+ from <HOST> not allowed because not in any group\s*$ ^%(__prefix_line)srefused connect from \S+ \(<HOST>\)\s*$ ^%(__prefix_line)sUser .+ from <HOST> not allowed because a group is listed in DenyGroups\s*$ ^%(__prefix_line)sUser .+ from <HOST> not allowed because none of user's groups are listed in AllowGroups\s*$
I know that regular expressions aren’t for everyone, and that this example may not mean a whole lot. So if you want to learn more about them, check out these two excellent sites:
Assuming that you’ve copied jail.conf to jail.local, open the new file in your editor of choice; I can’t help but use VIM. There are a number of key settings that you can configure to suit your particular needs; these are:
|This is the number of seconds that you want a malicious host to be banned for. By default, it’s set to |
|The email address to send failure reports to. You could leave it set at its default value of |
|The name of the account that sends the reports. Change it from its default value of |
After you’ve configured these settings, the next thing to do is to ensure
action is set appropriately.
This is the default action to take when an attack is detected.
By default it’s set to
action_, which bans the IP address.
However, that doesn’t help us a whole lot if we want to know what’s going on.
So I suggest you change it to
This will ban the IP and send an e-mail, along with a whois report, to the email address defined by
Here are all the available options that you can use.
|The simplest action to take: ban only|
|Ban & send an e-mail with whois report to the destemail.|
|Ban & send an e-mail with whois report and relevant log lines to the destemail.|
|Ban & send a xarf e-mail to abuse contact of IP address and include relevant log lines to the destemail.|
|Ban IP on CloudFlare & send an e-mail with whois report and relevant log lines to the destemail.|
|Report block via blocklist.de fail2ban reporting service API|
|Report ban via badips.com, and use as blacklist|
With the configuration set, we now need to check that the ssh jail is enabled, and configured, as in the example below.
[ssh] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 6
With that change made, you lastly need to reload Fail2Ban, by running
service fail2ban reload, so that all the changes are enabled.
When you do that, you should see the output below.
sudo service fail2ban reload * Reloading authentication failure monitor fail2ban
With that done, let’s do a quick check, to see that Fail2Ban is running and that the ssh jail’s active. To do that, we’ll use fail2ban-client, as follows:
sudo fail2ban-client status Status |- Number of jail: 1 `- Jail list: ssh
You can see that one jail is listed, and that it’s SSH. So we’re ready to test that our configuration will protect against malicious SSH logins.
To do this, attempt to login to your test server using ssh. Login with any username that comes to mind and supply any password. As the username and passwords are wrong, you’ll be prompted to try again, twice. Do this, and then attempt to login again with another bogus username and password.
On the sixth attempt, the login attempt should hang, because it’s waiting for a timeout to occur.
At this point, it’s now time to use
fail2ban-client, to check if our IP address has been banned.
To do that, we’ll use the status argument again, but this time specifying the ssh jail, as follows.
sudo fail2ban-client status ssh Status for the jail: ssh |- filter | |- File list: /var/log/auth.log | |- Currently failed: 0 | `- Total failed: 6 `- action |- Currently banned: 1 | `- IP list: 172.28.128.3 `- Total banned: 1
You can see that there is one banned IP, which is our IP: 172.28.128.3. You should have received an email as well. So have a look at your email inbox.
Now that we’ve successfully banned an IP, what if we want to unban it? To do that, we can again use fail2ban-client, and tell it to unban a specific IP, as in the following example.
sudo fail2ban-client set ssh unbanip 172.28.128.3 172.28.128.3
After we do that, we need to check that it’s been removed from the ban list, so we’ll check the ssh jail’s status again, as we did before. You can see in the following example that it’s no longer banned.
sudo fail2ban-client status ssh Status for the jail: ssh |- filter | |- File list: /var/log/auth.log | |- Currently failed: 0 | `- Total failed: 6 `- action |- Currently banned: 0 | `- IP list: `- Total banned: 1
And that’s how to use Fail2Ban to protect your servers against malicious, brute force login attempts. That said, it’s only just scratched the surface of what Fail2Ban can do.
However, please be aware that Fail2Ban — especially with this minimalist configuration — doesn’t provide 100% protection against attacks on your websites and servers. Yet, it’s an excellent way to start!
If nothing else, now you have far greater protection than you did before, and you’re far more aware of just how many attacks are being made against your servers on an hour-by-hour basis.
If you like what you’ve learned so far, I strongly encourage you to keep learning. There’s so much to uncover by reading them.
If you’re experienced with Fail2Ban, I’d love to hear about your configurations and experience with it in the comments.