Harden the default SSH configuration settings to reduce your system’s attack surface on both your server and client. Also, install and configure fail2ban in your server.
Why?
SSH is the privileged gateway for remote administration of almost any server or network device (in many cases, this includes personal computers and/or gadgets), which makes it a prime target for attackers.
We must harden SSH to prevent attackers from exploiting weak configurations and gaining access to our devices, thereby protecting both our devices and the rest of our network.
The goal of hardening is to eliminate (or reduce) inherent vulnerabilities that may exist in the default configuration of a service.
Keep your system updated ;)
Keeping your system updated is the primary way to patch security vulnerabilities. Therefore, the first step, and one of the most important steps in hardening (both on the server side and on the client side) is to keep your system updated.
Server hardening
The configuration file
To harden SSH, on the server side, we are going to change certain settings in the SSH configuration file: /etc/ssh/sshd_config.
To configure the values, we can uncomment the lines (commented out) and edit the values. However, I prefer to leave the original line commented out and place the line with the new value directly below it. This is a quick way to see if we have modified a default configuration (and what its default value was) when we review the file, when we add new configurations, or when we modify existing settings. If you prefer to edit the values directly, I recommend you make a backup of the original configuration file.
After changing the settings, we’ll need to restart the SSH service for the changes to take effect.
Change the default SSH port
Security by obscurity is never the solution, and port change is hardly a silver bullet, but it will deter automated attacks.
Port 1023 # Generally used for testing
Changing the port can create a security issue. The catch here is that changing the port can create a security issue called “Non-Privileged Port Binding” or “Ephemeral Port Hijacking”. While Linux restricts privileged ports (1-1023) to the root user, any unprivileged local user is able to bind and listen to unprivileged ports (1024-65535). Therefore, if our SSH server is running on, say, port 2222, and it crashes, a local user could start a fake SSH server on this port.
My choice here is to implement Port Forwarding from the external firewall. I would open port 22222 in the firewall and redirect that traffic to port 22 of the machine running the SSH server.
Disable X11 forwarding
If we are not going to use graphical applications via SSH, it is best to disable it. The main reason for disabling X11 forwarding is the possibility of an attacker gaining access to your local X server session if the remote SSH server is compromised.
X11Forwarding no
Disable agent forwarding
We will disable agent forwarding because it creates a security risk by allowing the compromissed remote machine to use our local SSH private keys for any subsequent connection.
AllowAgentForwarding no
Restrict root login
On a well-configured system, the root account should be disabled already. Disabling the root account and elevating privileges using sudo is a good security practice. Anyway, we are going to disable it:
PermitRootLogin no
Limit users and/or groups that can log in:
We will specify which users and/or groups are allowed to access through SSH.
1
2
AllowUsers rubenhortas rhortas
AllowGroups rubenhortas rhortas ssh
The users and groups are examples. You have to add your own users and groups.
If a user is listed in AllowUsers or belongs to a group listed in AllowGroups, they are allowed access. If neither AllowUsers nor AllowGroups is specified, all users are permitted to log in (assuming they have credentials).
You should consider using only one approach (
AllowUsersorAllowGroups) if possible. My personal approach is to use onlyAllowUsersbecause I have few users to manage.
Configure the idle timeout
We are going to set up an inactiviy timeout to prevent sessions from remaining open indefinitely.
1
2
ClientAliveInterval 300 # The server will send the client a message every 300 seconds if there is no activity
ClientAliveCountMax 2 # If the client does not respond to 2 messages in 10 minutes (300 x 2 = 600 seconds), the connection is terminated
At this point, we have to be careful not to set values so restrictive that they cut off our connections.
Alternatively, for non-SSH specific shell timeouts, consider using the TMOUT environment variable in the shell profile.
Disable password authentication.
Use only public/private key authentication.
You can learn how to use RSA keys as authentication method here: SSH without password, using RSA keys
After we have RSA key authentication set up and working, we are going to disable PasswordAuthentication:
PasswordAuthentication no
Explicity enforce Protocol 2
While modern SSH server software defaults to using the secure Protocol 2, and the modern and secure approach is to simply not specify the protocol, explicitly setting the directive could be a critical step in hardening our server. SSH Protocol 1is insecure and has known severe vulnerabilities. Explicty setting the configuration ensures that Protocol 1is never accepted, even if a client attempts to negotiate it.
Protocol 2
Force the use of modern algorithms
We will define a strict list of preferred modern algorithms. The server’s list will determine the algorithms used if the client supports them.
Key Exchange Algorithms
We need to harden our Key Exchange Algorithms using algorithms that uses modern elliptic curve cryptography (ECC) and strong Diffie-Hellman methods, offering excellent security and performance.
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
MACs (integrity)
MACs (Message Authentication Codes) ensure the data has not been tampered with in transit. We should prioritize Encrypt-then-MAC (EtM) algorithms.
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
Ciphers (encryption)
Ciphers encrypt the data channel. It’s best to prioritize Authenticated Encryption with Associated Data (AEAD) ciphers like chacha20-poly1305 and aes-gcm.
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
Reboot SSH service
We have to restart the SSH service for the changes to take effect.
sudo systemctl restart ssh
Bonus: fail2ban
fail2ban monitors failed login attempts in the logs and blocks the IPs addresses they originate from in the firewall.
Installation
sudo apt install fail2ban
Configuration
The default configuration is acceptable, but I preffer to tighten it. To configure fail2ban we will edit the file /etc/fail2ban/jail.local. My /etc/fail2ban/jail.local file (commented) as an example:
1
2
3
4
5
6
7
8
[sshd] # Defines a jail targeting the SSH daemon
enabled = true # Activates this jail
backend = systemd # Method to monitor the logs
port = ssh
filter = sshd # Use the predefined sshd filter to detect SSH authentication failures in the logs
maxretry = 1 # Maximum number of failed login attempts allowed before an IP address is banned
findtime = 1m # Time window during which the maxretry count is calculated
bantime = 1m # Ban duration
There was a time when I had the ban set to last forever, but it ended up with so many IPs that it degraded performance.
My fail2ban configuration could be quite aggressive. Since I have disabled password authentication, any failed login attempt is highly unlikely to be a legitimate user. Therefore, setting maxretry = 1 and findtime = 1m is effective, because any password attempt will be considereded an attack, justifying the aggressive ban.
Enable and start fail2ban service
1
2
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Client hardening
The configuration files
To harden SSH, on the client side, we are going to change certain settings in one of the two SSH configuration files: /etc/ssh/ssh_config or ~/.ssh/config.
/etc/ssh/ssh_configThis file holds the system-wide configuration. It defines the default configuration for all users on the system.
To configure the values, we can uncomment the lines (if commented out) and edit the values. However, as I said before, I prefer to leave the original line commented out and place the line with the new value directly below it. If you prefer to edit the values directly, I recommend you make a backup of the original configuration file.
~/.ssh/configThis file defines custom configurations for the user and specific hosts or groups of hosts:
- Specific host configuration
To add a specific host, you create a new configuration block starting with the
Hostoption and a unique alias for that host:1 2 3 4 5 6 7 8 9 10 11 12
Host rubenhortas-dev HostName 192.168.1.1 PreferredAuthentications publickey PasswordAuthentication no HostBasedAuthentication no StrictHostKeyChecking ask KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256 MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com ForwardX11 no ForwardAgent no ...- Default configuration for all hosts
The
Host *entry applies to all hosts, but its settings are applied hierarchically. TheHost *settings will be applied to every single connection, unless they are overridden by a more specificHostentry. We will add the hardening settings in this block.1 2 3 4 5 6 7 8 9 10 11
Host * PreferredAuthentications publickey PasswordAuthentication no HostBasedAuthentication no StrictHostKeyChecking ask KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256 MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com ForwardX11 no ForwardAgent no ...
Restrict key files permissions
1
2
3
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_*
chmod 64 ~/.ssh/known_hosts
Private keys
Better with passphrase? The passphrase prevents the attacker from using the keys if they fall into the wrong hands.
Prefer public key authentication
PreferredAuthentications publickey
This forces the use of public keys as form of authentication.
Prioritizing Public Keys
PasswordAuthentication no
This ensures that our client always prioritizes the use of public keys above all other password-based methods.
Disable host based authentication
HostBasedAuthentication no
Host based authentication is less secure.
Key verification
We must force the client to verify that the server’s key matches the key stored in the known_hosts file to prevent Man-In-The-Middle (MITM) attacks. The relevant configuration options are setting StrictHostKeyChecking to ask or yes.
We need a balance between security and convenience. My preferred approach is to configure StrictHostKeyChecking ask for all hosts by default, and set StrictHostKeyChecking yes to sensitive hosts (like my own).
A change in a host key is usually due to legitimate administrative reasons (e.g. reinstallations, replacement, changes in key/type algorithms…). However, althoug less common, it could be due to malicious acts.
There are certain types of hosts where key changes are not uncommon, such as Capture The Flag (CTF) challenge servers or frequently rebuild virtual machines. But, on my own hosts, a change in keys should never happen without my knowledge.
By setting StrickHostKeyChecking ask globally, as default, and StrictHostKeyChecking yes for critical hosts, I can connect to a host susceptible to key changes and, if the key had changed, I can decide whether to accept the new key. Crucially, if the key had changed on a host where it should not, the strict setting will prevent the connection entirelly, inmediately alerting me to a potential security issue.
if the key had changed on a host where should not, I will find out something is wrong when I connect.
StrictHostKeyChecking ask
1
2
* First connection: the SSH client will prompt you to manually confirm if you trust the server's key before adding it to your `known_hosts` file.
* Server key changed: The connection will be refused.
StrictHostKeyChecking yes
1
2
3
* First connection: The SSH client will refuse the connection immediately if the host's key is not already present in your `known_hosts` file.
You must manually add the host key before attempting to connect.
* Server key changed: The connection will be refused.
Force the use of modern algorithms
On the client side, we will enforce the same strong ciphers that we chose for the server. This will ensure that, even if we connect to a legacy server, we will use the best available options.
Key Exchange Algorithms
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
MACs (integrity)
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
Ciphers (Encryption)
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
Disable X11 forwarding
As on the server side, we will disable X11 forwarding to prevent exposing our graphical session.
ForwardX11 no
Disable agent forwarding
As on the server side, we will disable agent forwarding to prevent that a compromised server from using our keys to (potentially connect to other servers.
ForwardAgent no
Thanks to Rodrigo Rega and Alberto Mouriz for the advice.
Thanks for reading! ;)