SSH Hardening: A Primer
SSH is a wonderful service that is standard on nearly all Linux systems. It is a service that, when configured correctly, can provide quick and secure access to a system. However, when configured incorrectly SSH can offer attackers an easy route into your systems unabated by even simple safety measures.
The foundations of SSH hardening are mostly found within the configuration of sshd(or what ever ssh server implementation you might have) or the services surrounding your SSH environment. Additionally, good SSH practices also include good general system privilege practices.
In this post we’ll cover some basic configuration parameters, a couple services that can help, and some simple changes to make with your users to ensure that you become less of a target.
SSH Config/User Setup: The Basics
Most edits can be made directly in the /etc/ssh/sshd_config
file if using standard SSH that ships with most Linux Distros
Below I will put some directives I commonly change. Some of these might already be uncommented in the conf file, thus make sure you apply them correctly or at the end of the file (though it’s better to only have one of each directive uncommented at a time):
Port 2222 # or some other port, usually something other than 2222 is better
PermitRootLogin no
MaxAuthTries 3
PasswordAuthentication no
SSH port
The most obvious place to start with SSH hardening is the SSH server config. The most basic change you can make (and typically the least effective) is to change your ssh port. On systems that use selinux you will need to clear this with selinux like so:
semanage port -a -t ssh_port_t -p tcp ${port_number}
Most people go for 2222, and this will only deter the most basic of attempts. Anyone with nmap (read: everyone) will see through this trick. However, picking something other than 22 and 2222 could substantially reduce scanner activity in some cases.
Max Auth Tries
This is another that won’t deter the more determined attackers but can help trim your logs, especially when paired with some other options talked about later in this guide. By setting the MaxAuthRetries
value lower (say 3?) you can interrupt attempts and shake some less sophisticated bots quicker as the default is typically 6.
PermitRootLogin no & PasswordAuthentication no
These two combined along with a good key management and password policy will be your strongest defense in the config department. SSH preventing root login (for those of you new to the Linux space) stops people from obtaining the figurative keys to the kingdom.
The literal keys to the kingdom however are going to be your SSH keys, generated on your local (the one you use to ssh) using an ssh-keygen command similar to the following
$ ssh-keygen -t rsa -b 4096 -c "Home Desktop"
In the -c option you can put what ever you want for a comment, it’s typically best to have 1 key per user, per device that will access your server, and denote them by user and device. You should also think about giving your keys passwords as well, but that is definitely optional.
Remember: Your public key should go on any system you need access to, but your private key should stay firmly on the device that generated it. Generally, you should create a new one for each device though this is rarely followed.
The Root Access Situation
So you don’t have remote root access, and you have a privileged user to access root, Key access only for remote users, etc… do you really need people to log in AS root?
Probably not.
So, usually, I would give root something like a hashed phrase as a password. Like “My root password is very long” and pass that through base64 (please pick something else!). Make your root password near impossible to guess, then the only way to access root is through your privileged users, who can only be accessed via their SSH keys! Downside: If you ever lose control of your privileged user, you are effectively locked out of the system. But same could be said if you lose control of root. However, many people are adopting this method, even going as far as not giving root direct login access by removing its password entirely/making it extremely long and random (See Ubuntu and MacOS). You can outright lock the root account as well by using passwd -l
.
Of course your privileged users still need passwords to give them root access, but a good enough password policy (using either PAM or authconfig) will keep them plenty secure. What you set as a password policy is up to you but most standards say 8-character minimum with a minimum complexity like 3 different character classes. Remember this password is needed for escalating privileges, or logging in locally (i.e., through a console or su
). If it’s weak, an attacker CAN use it for escalation if they achieve code execution.
Protecting Password Authentication
So, if you’re not quite comfortable with the policy of disallowing password login, there are still some options available. You can still use these methods combined with the above, but they will only really serve to reduce the number of logs you receive. They could also just be precautions for if you ever need password auth in the future.
First, we can look at network options, since the network isn’t entirely aware of SSH states we can’t tell something like iptables to defend against failed login attempts. We can use a log based system like fail2ban or denyhosts, though these alone can be subject to log manipulation. Finally, we have user lockouts, these are dangerous as some users only have remote access to their accounts. There are some steps you can take to prevent total lock out in these scenarios that we can cover shortly.
Rate Limiting Connections
Depending on the system you’re using, iptables may or may not be directly available as UFW has become standard in Ubuntu, whereas Firewalld has become standard on RHEL based OSes. The general theory stays the same however: if an IP address tries to connect to the SSH port too many times in “X” period fo time, drop packets from that IP address.
CAUTION: You CAN permanently lock yourself out of your system with these rules. You should strongly consider using iptables-apply to test ANY prohibitive rules before permanently applying these rules. Additionally you can whitelist your IP address specifically.
Here we see a basic command to rate limit connection attempts to the standard SSH port:
iptables -A INPUT -p tcp --dport ssh -m conntrack --ctstate NEW -m recent --set
iptables -A INPUT -p tcp --dport ssh -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 6 -j DROP
replace
--dport ssh
with your custom port number if your SSH port is different. Also credit to Sander Knape for this snippet
In this command you will block 6 connections with in 60 seconds, please be aware this doesn’t care what happens in those connections though. You can always augment the seconds and hitcount directives to take different values and restrict/loosen this rule however you see fit.
If you are using firewalld you can try to use something similar to this:
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT_direct 0 -p tcp --dport 22 -m state --state NEW -m recent --set
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT_direct 1 -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 30 --hitcount 4 -j REJECT --reject-with tcp-reset
firewall-cmd --reload
again, replace
--dport 22
with your custom SSH port if you set one
Be aware, omitting the --permanent
option from these commands will keep them from completely locking you out in the event of a mistake but Firewalld has no equivalent of iptables-apply that I am aware of. The same tweaks can be made here with the seconds and hitcount.
And finally for UFW:
ufw limit 22/tcp comment 'SSH rate limiting'
UFW is the easiest option (by design) but because of its reduced functionality it also prevents you from tweaking the rate limit. I have done a little research into this and essentially you’ll have to modify UFW itself if you want to tweak this behavior easily.
Port Knocking
Next is one that could be more beneficial to even you no-password users out there. This is a little more advanced and considred over kill in many cases.
Port knocking is simply hiding ports until a specified order of closed ports are contacted or “knocked”. After, the firewall dynamically changes to open the requested port. This is sneaky and very useful but definitely reaching into the more complex/prohibitive measures.
While I could go into the specifics of how to configure it I will, instead, offer this guide from Tech Mint. It covers the general idea pretty well, and if you’re getting this deep in the weeds you’ll be able to find the alternative configurations (for iptables and firewalld and such).
Port knocking will almost certainly eliminate failed SSH attempts to your server as a result of brute force attempts but it is going to substantially increase the overhead in connecting to your server so keep that in mind. One way to reduce the manual labor would be to create a function or script to knock then ssh into your target system like so:
#!/bin/bash
knock $1 <your_knock_sequence> &&\ # replace <your_knock_sequence>
ssh -p22 -i~/.ssh/my_id.rsa user@$1 ||\ # <replace -p22 with your port, replace my_id.rsa with your ssh keys, and user with your user>
echo "I failed :("
This script would, when executed, take the first argument (which should be a hostname or IP address) and run knock and ssh against it in sequence, and give a befitting frowny face if it fails :(
Fail2Ban
Fail2Ban (F2B) and other log based banning systems like denyhosts are nice because they are aware of the results of the connection. Bots who manage their rates properly will get around iptable rate limiting sure, but they will rarely make it past these methods. There are many approaches and they’re all pretty well documented but there are a few main factors to keep in mind:
- to alert or not to alert (and how)
- should you whitelist an IP Address? (do you have a static IP?)
- how long to ban for
- if & when to permaban an IP address
These are all philiosophical questions that I can’t answer for you but personally I go with no alerts, a whitelisted known good IP address (I have a static home address), a standard timeban time and for a while I used to have F2B monitor its OWN log to perma ban an IP after X number of bans. This was a nice feature that ruled out forgetful users but after I moved away from Password auth I just let F2B’s regular exponential ban time feature do its magic. Honestly, F2B’s standard settings for SSH are almost perfect, with the only change I’d make being to the bantime.rndtime
directive which is well defined in the conf file itself as “the max number of seconds using for mixing with random time to prevent “clever” botnets calculate exact time IP can be unbanned again”.
Otherwise, there are plenty of guides out there that tell you how to defend SSH with Fail2Ban such as this one from Linode. Just make sure that if you use a non-standard firewall you install the appropriate plugin for F2B!
F2B is also a great tool for other protocols, such as FTP and HTTP, really anything that can log to a file you can make a F2B jail for it. As stated above you can even use F2B to monitor its own logs to force permabans on IP addresses that get banned multiple times.
Potential Backup Plans For Access
So you’re worried about getting locked out of your system? You’ll need a way to access the system that isn’t going to get locked out. Most VPSes offer console access, and so long as you remember your privileged user’s password you should be good. However, there are scenarios where you need what I refer to as an “Operator” account. I won’t go into the technical details on how to accomplish this in this post (maybe in the future) but a rough overview is as follows:
- create a user who, in the SSH config, is not allowed to ssh into the system
- do not allow users to
su
to this user - restrict ptty to this user (this prevents pseudo terminals from being allocated)
- give this user a password and sudo
Summary
The process of hardening SSH is relatively easy but it can require a change to many people’s idea of what a normal SSH workflow may be. Additionally, if your password based authentication is a must there are still some great steps you can take to secure your implementation. SSH is a great utility for managing your *nix like systems, and when configured properly can be one of the most secure methods.