Protecting SSH with Fail2ban

SSH bruteforce log

SSH allows you to log in to a remote computer or server and control it through a command-line interface. However, because SSH is exposed to the internet; attackers can try to log in by trying various username and password combinations.

A good way to protect SSH would be to ban an IP address from logging in if there are too many failed login attempts. Fail2ban does this right out of the box. In addition, you can even configure Fail2ban to protect other applications, like web servers.

We’ll cover how to protect SSH with Fail2ban in this post.

Installing Fail2ban

On Debian and Ubuntu, you can install Fail2ban with:

sudo apt update
sudo apt install fail2ban

On CentOS, you should first enable the EPEL repository and install Fail2ban.

sudo yum -y install epel-release
sudo yum -y install fail2ban

Having enabled it, you should enable and start the service.

sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Fail2ban should now protect SSH out of the box. If Fail2ban notices six failed login attempts in the last ten minutes, then it blocks that IP for ten minutes. However, you can change this policy if you wish.

The basics of Fail2ban

So how does that work? Put simply, Fail2ban is a daemon that monitors logs and takes actions based on their contents. It is driven by three types of configuration:

  • Filters specify certain patterns of text that Fail2ban should recognize in log files.
  • Actions are things Fail2ban can do.
  • Jails tell Fail2ban to match a filter on some logs. When the number of matches goes beyond a certain limit specified in the jail, Fail2ban takes an action specified in the jail.

Fail2ban comes with a jail instructing it to look at system logs and take actions against attacks on SSH. The default action (which is discussed later in detail) adds iptables rules to block out attackers.

When you make changes to the Fail2ban configuration, you need to restart it for changes to come into effect:

sudo systemctl restart fail2ban

On older, sysvinit based systems, the command is a bit different:

sudo service fail2ban restart

Configuring the jail

While the defaults will suffice for most, sometimes you may need to change things so that it suits your needs better.

In order to do this, you should determine the jail that protects SSH. Run this on the remote system:

sudo iptables -L -n | sed -nr 's/Chain (fail2ban|f2b)-([^ ]+).*/\2/p'

You will see something like ssh or sshd as an output. This is the jail we were looking for. For the purposes of this tutorial, we’ll assume the jail is named sshd.

Now, create /etc/fail2ban/jail.local with a text editor such as nano:

sudo nano /etc/fail2ban/jail.local

Type the jail name you just found out inside square brackets, like so:

[sshd]

This begins the configuration of a jail. The rules of the jail will go below it.

Say, you want to block IP addresses for a day, if they have tried to make 10 login attempts within 12 hours. To do this, type in the following text below [sshd]:

maxretry = 10
findtime = 43200
bantime = 86400

The file would end up looking like this:

[sshd]
maxretry = 10
findtime = 43200
bantime = 86400

Here:

  • maxretry controls the maximum number of allowed retries.
  • findtime specifies the time window (in seconds) which should be considered for banning an IP.
  • bantime specifies the time window (in seconds) for which the IP address will be banned.

If you have moved SSH to a different port, you can also specify the port number with port = <port_number> in this file.

Restart Fail2ban for these changes to come into effect.

Configuring the action

By default, Fail2ban uses the iptables-multiport action, which rejects packets with a “port unreachable” message. If you want to drop packets instead of rejecting them, create /etc/fail2ban/action.d/iptables-common.local and type in the following:

[Init]
blocktype = DROP

On some older distributions, you may have to save the file in /etc/fail2ban/action.d/iptables-blocktype.local instead.

If you want to take an entirely custom action, you have to first define an action file in /etc/fail2ban/action.d/. A typical action file looks like this:

[Definition]
actionstart = ...
actionstop = ...
actioncheck = ...
actionban = ...
actionunban = ...

As their names suggest, the actionstart and actionstop lets you specify initialization commands that will be run when Fail2ban starts up and shuts down. actionban and actionunban specify the commands which will be run when Fail2ban wants to block an IP. In addition, Fail2ban may also need to verify whether an IP is already blocked, and the command specified in actioncheck helps it to do so.

When specifying these commands, you can use the <ip> placeholder to get the IP address of the attacker.

After you’ve written your action file, in the SSH jail defined in /etc/fail2ban/jail.local, you can set the action by setting the following:

banaction = my-action

Blocking repeat offenders with multiple jails

As a server administrator, you may have noticed that there are some IPs that keep brute forcing over and over, despite the bans. You could work around this by setting a low value of maxretry and findtime and a high value of bantime, but this risks locking out legitimate users. Fortunately, you can block these attacks by setting up multiple jails.

For our example, we’re going to define a jail named sshlongterm. It will block an IP for a week if there are 35 failed attempts login attempts over 3 days.

However, before we get started, we need the configuration of the default jail. If you open /etc/fail2ban/jail.conf, you will find a default configuration of the SSH jail. On Ubuntu 16.04, the default jail is named sshd and the configuration looks like this:

[sshd]
port    = ssh
logpath = %(sshd_log)s

Copy these rules over to /etc/fail2ban/jail.local, under the definition of the sshlongterm jail. The file should end up looking like this:

[sshd]
; rules for the default SSH jail if you've customized it.

[sshlongterm]
port    = ssh
logpath = %(sshd_log)s

If you don’t have a filter = <value> rule in the default configuration (like our case), you need to define a filter explicitly for this jail:

filter = sshd

Now, you need to set up the primary rules for the jail. Add the following lines:

maxretry  = 35
findtime  = 259200
bantime   = 608400
enabled   = true

These settings allow up to 35 failed logins over 3 days before blocking the IP for a week.

The complete jail would end up looking like this:

[sshlongterm]
port      = ssh
logpath   = %(sshd_log)s
filter    = sshd
maxretry  = 35
findtime  = 259200
bantime   = 608400
enabled   = true

Restart fail2ban for these changes to take effect.

The default jail as well as the sshlongterm jail should now work in conjunction. Short term attacks will be handled by the default jail, and the long term attacks handled by our own jail.

Conclusion

As we’ve seen, Fail2ban protects SSH right away. With a little bit of configuration, it can make massive brute force attacks a trivial problem.

If you liked this post, please share it 🙂

You may also like...