1. Introduction

There are many strategies at hand to secure our servers’ administrative services. Segmenting a subnet specific to administrative purposes, administrative VPN, logging gateways, and source IP filtering, to name a few.

In this tutorial, we’ll discuss some of those strategies. More specifically, how to limit SSH access to specific clients by their IP addresses. Some of the techniques we’ll discuss are useful for other services as well.

2. Administrative Services Caution

From a security perspective, one of the first guidelines is to disable any service that is not necessary (check this very interesting NIST guide). That means that we should disable any unnecessary, non-essential, operating system services.

However, some services are needed for the system operation and maintenance itself. In this case, we must assess the narrower possible set of privileges. That way, we’ll try to limit the attack surface to a minimum.

Administrative services, due to their importance, should be dealt with even extra care since they can be used to inflict really serious damage to our business.

3. TCP Wrappers

One of the first solutions that came up to block or enable service access was TCP wrappers. It has been around since the early nineties, and it is one of the first host-based ACLs (Access Control List).

It has two main integration modes. The applications can be linked with its library libwrap0 (installed by default in any major Linux distributions) or it can use its proxy utility, tcpd which directly or through some network super-server daemon (like xinetd or inetd).

Even though the OpenSSH server lost its native TCP Wrappers support in version 6.7, it can still be used on many legacy Unix boxes. In the newer OpenSSH versions, it is still possible to use it with the xinetd super-server daemon.

The main advantage of TCP Wrappers is the simplicity of their configuration. If our version supports it, we can control its behavior by tweaking two files. To deny every host we add a wildcard to the /etc/hosts.deny file:

sshd : ALL

Now we can enable access to specific hosts by tweaking the /etc/hosts.allow file:

sshd :
sshd :
sshd : [::1]
sshd : myhost.local.com

Of course, if we use a version of OpenSSH that has no TCP Wrappers support (a very good guess for any system installed or update after 2014), this will not work.

4. Firewalls, IP Filters, and IP Tables

The more usual way of blocking SSH access from IP Addresses is by the use of IP Filtering. Again, there are two approaches. The first is to use a Network Firewall to protect any administrative level access to your servers. The diagram below shows how it works:


As we can see, the Firewall can monitor and apply ACL rules to any network traffic that flows toward it. However, that is exactly where lies its main limitation. If an attacker finds an alternative path which not under the Firewall control, the attack might get through. On the diagram, while clients B en C has their access moderated by the Firewall, client A doesn’t, being in the server’s network.

That is why the protection should be designed as layered. Each protection layer, when breached, can give notice of the issue promptly. By doing so, an attentive administrator can take the countermeasures to menaces, before they create real damage.

A Network Firewall is an essential part of our protective measures – mandatory, even. However, it is common to enforce its more important rules on the host. For that, we can use the Operating System’s IP filtering. For instance, Linux can use IP Tables:

iptables -A INPUT -p tcp --dport 22 --source -j ACCEPT     # Add SSH access to a single host
iptables -A INPUT -p tcp --dport 22 --source -j ACCEPT  # Add SSH access to a subnetwork
iptables -A INPUT -p tcp --dport 22 -j DROP                            # Drop all other sources

IPTables are extremely powerful. It can act as a full Network Firewall if its machine has a direct connection to two or more networks. But also it can load balance traffic, redirect and remap destination Addresses and ports, and simulate network issues, to name a few.

5. SSH Configuration

Along with the options above, we might want to enforce some security measures on the /etc/sshd_config file.

There are a lot of configurations that will help us to set the appropriate security in diverse usage scenarios. When we need to have an ssh server open to the Internet, it is best to use stronger authentication methods, like public/private key pairs.

The SSH daemon itself can use public/private keys, along with whatever alternatives the Plugable Authentication Modules (PAM) system allows: Kerberos SSO, multiple authentication factors, including One-Time Passwords, no name a few.

The best solution depends on the specific application scenario. Besides the authentication, it is useful to verify the SSH server’s other security configurations. For instance:

  • Disable root access: PermitRootLogin yes/no
  • Disable tunnels and forwarding: <em>PermitTunnel yes/no; AllowTcpForwarding yes/no; DisableForwarding; yes/no</em>
  • Use PAM: <em>UsePAM yes/no</em>
  • Disable password authentication: PasswordAuthentication no
  • Enable key-based authentication: AuthenticationMethods publickey
  • Allow users and groups: AllowUsers / AllowGroups
  • Deny users and groups: DenyUsers / DenyGroups
  • Chroot users sessions: ChrootDirectory chroot_path

With PAM authentication (default in all major distributions) we can create a lot of different options to limit access to specific IPs and users. The /etc/security/access.conf file can create access and deny rules based on users/address pairs, like these:

# Allows user foo and members for admins group from the console and the specified IPv6 address and IPv4 subnet
+ : @admins foo : LOCAL 2001:db8:0:101::/64

# Disallow console logins to all but the shutdown, sync and all other accounts, which are a member of the wheel group.
- : ALL EXCEPT (wheel) shutdown sync : LOCAL

# Block all other users from all other sources
- : ALL : ALL

6. Dynamic Blocking

If our firewall support blocking insistent tries from IPs, we must enable it. That way, insistent attempts from the same IPs will be blacklisted and quarantined. We can also use IP Tables or some additional software to get the same results.

First, to throttle insistent SSH attempts we can use these IP Tables rules:

# Enable recent rules for incoming SSH
iptables -A INPUT -p tcp -m tcp --dport 22 -m state --state NEW -m recent --set --name SSH --rsource -j ACCEPT
# Rate limit SSH connections 5 (--hitcound) attempts for each 60s (--seconds)
iptables -A INPUT -p tcp -m tcp --dport 22 -m recent --update --seconds 60 --hitcount 5 --rttl --name SSH --rsource -j LOG --log-prefix "SSH_BAN"
# Log the blocked attempts (optional)
iptables -A INPUT -p tcp -m tcp --dport 22 -m recent --update --seconds 60 --hitcount 5 --rttl --name SSH --rsource -j DROP

Unfortunately, this approach does not take into account only the unsuccessful or not. It blocks the connection attempts regardless. If we want to consider only the failed attempts we must use a more elaborate solution.

One way is to write a script that continuously monitors the SSH daemon log files, and create IP Tables rules on the fly, removing them after a quarantine period. That approach already is conveniently packed in many open-source solutions such as denyhostsfail2ban, and sshguard.

Another approach is the PAM Auto Black List module, pam_abl. This module can automatically blacklist users and IPs based on failed authentication attempts. One highlight is that, as it is a PAM module, it can be used by any service using it. In Ubuntu, just install it with:

# First install pam_abl
sudo apt-get install -y libpam-abl

At the end of the installation, it should be added by the installer to the PAM stack configuration in the /etc/pam.d/common_auth:

# here are the per-package modules (the "Primary" block)
auth    required                        pam_abl.so config=/etc/security/pam_abl.conf

The configuration file/etc/security/pam_abl.conf will have the quarantine times for users (user_rule) and hosts (host_rule), as the respective default whitelists (users or hosts that are will never enter the blacklists). The command-line utility pam_abl will show the current contents of the blocklist. It also can add or remove hosts and users from them:

# pam_abl
Failed users:
	foo (15)
		Blocked based on rule [*/sshd]
Failed hosts: (15)
		Not blocking

7. Conclusion

In this tutorial, we discussed measures to limit the risk of unauthorized access to our servers’ SSH services. As we’ve shown that best practice is to not rely only on a single protective measure.

Whenever possible, we must stack protections such that an external attacker must invest time and effort to breach before inflicting any damage. So, combining some of these techniques is a good starting point. Depending on the exposure and risk level, we must consider using stronger authentication, limiting the IP addresses that can access our servers, throttling the connections to slow down brute force attempts, and quarantining offending hosts, on top of the regular perimeter network firewalls.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.