Five Minutes to an Even More Secure SSH

lock

This is a followup article to Five Minutes to a More Secure SSH. My impetus for writing the original was seeing too many client's servers left in a default state where they are vulnerable to brute-force attacks. In it, I basically advocate three things:

Those recommendations still hold true and I would encourage you to follow them. However, OpenSSH has many features and there is more you can do to secure your SSH servers, without resorting to external software.

Refresher and Important Notes: The main OpenSSH server configuration file is called sshd_config and will typically be in the /etc/ssh or /etc/sshd directories. Like all of the configuration files used by OpenSSH, it is in plain text and so can be edited with any text editor. After editing your sshd_config file, you will need to reload your SSH server's configuration - restarting the SSH daemon is not necessary. The command typically looks like this (this is on Debian or Ubuntu):

/etc/init.d/ssh reload

or (on Red Hat/Fedora):

service sshd reload

Also be careful not to lock yourself out of your SSH server when experimenting with these access controls. It's a good idea to always have two SSH sessions into the server, and to always make backup of the relevant configuration files. If you log out of one session and get denied access, you still have one active session to fix things.

Restricting Users and Hosts

OpenSSH allows you to restrict users and groups by host or IP address. There are four different directives you can use in your sshd_config file (they are evaluated in this order):

DenyUsers AllowUsers DenyGroups AllowGroups

The format for all of them will be the same - a space-separated list of users or group names, with optional host names. Here is an example:

AllowUsers vader@10.0.0.1 maul@sproing.evillittleman.net sidious tyranus@*.evillitleman.net AllowGroups wheel staff

This tells sshd to only allow connections from the user vader and only from the IP address 10.0.0.1. The user maul is also allowed, but only from the host sproing.evillittleman.net. User sidious is allowed from anywhere, and the user tyranus is also allowed, from any host in the evillittleman.net domain (the asterisk matches zero or more characters).

The AllowGroups line allows login only from users whose primary group name or supplementary group list match one of 'wheel' or 'staff'.

Keep in mind that using AllowUsers or AllowGroups means that anyone not matching one of the supplied patterns will be denied access by default. Also, in order for sshd to allow access based on full or partial hostnames, it needs to do a DNS lookup on the incoming IP address. That means the connecting IP address must have a PTR (reverse) entry that maps back to a real hostname. These aren't hard to get if you have a static IP address, usually your ISP or server hosting provider can do this for you on request.

In addition to the asterisk in hostname or group patterns, you can use a question-mark to mean exactly one character, and an exclamation point to negate the sense of a match:

* - Matches zero or more characters
? - Matches exactly one character
! - Negates the host pattern match

Note: In my tests, using ! to negate the sense of the hostname match did not work with the AllowUsers directive. It only seems to work when used with authorized_keys file restrictions (see below).

Restricting Access and Commands

SSH has the concept of authorized keys. If you are using key-based auth, like I suggested in my first article, the user accounts on the SSH server will have an authorized_keys file (which is by default in the ~/.ssh directory of whatever user account you are logging into). This file lists the public keys, one per line, that are authorized for access to that account. Apart from just specifying which public keys are allowed access, there are a some more options that you can use to further restrict SSH sessions. Here are the most useful ones:

from='hostname1,hostname2,'' - Restricts access from the specified IP or hostname patterns
command='command' - Runs the specified command after authentication
no-pty - Does not allocate a pty (does not allow interactive login)
no-port-forwarding - Does not allow port forwarding

Here is an example showing part of an authorized_keys file:

from="deathstar.example.com,!jedi.example.com,10.0.0.?" ssh-rsa AAAAB5...2BQ== vader@evillittleman.net from="pitofdespair.example.com",command="ls",no-pty,no-port-forwarding ssh-dss AAAAZ7...22Q== droidQBX12@evillittleman.net

The first line allows login with the specified RSA key from deathstar.example.com, from any host with IP address in 10.0.0.[0-9], but not from the host jedi.example.com. The second line merely runs the 'ls' command whenever the specified DSA key is used - it does not allow any other commands to be run, does not allow interactive login, and does not allow port-forwarding. It also restricts the source of that key to the host pitofdespair.example.com.

Running sshd on a Non-Standard Port

Admittedly this is an attempt at 'security through obscurity', but that doesn't mean it's not useful when combined with other security measures. You may not be able to restrict access by hostname or IP, for example - you may always be sourcing your connections from a dynamic IP address, or you may not be able to get a proper PTR record created. It's quite simple, in your sshd_config file, just change Port=22 to Port=nnnnn (where nnnnn is some high port), then reload the sshd configuration. How do we pick a port number? Some are better than others. First, assume that most port scans are being done with Nmap, and take a look at the nmap-services file. This is a list of ports that Nmap will use by default if you don't specify a port range on the command line. It's probably a fair bet that most script-kiddies are using nmap is this manner. Just pick a high port not on this list, most nmap scans won't notice it. You can also use multiple Port= directives, meaning you can have sshd listen on multiple ports. Connecting to an alternate port is also very easy, use the following options depending on the command used:

ssh -p 65502 vader@deathstar.example.com sftp -oPort=65502 vader@deathstar.example.com scp -P 65502 deathstar_plans.doc vader@deathstar.example.com:

You can also edit your client's ~/.ssh/config file, and add the Port= directive to one of your host blocks:

... Host evil Hostname deathstar.example.com User vader Port 65502 ...

Then just connecting with the command ssh evil will connect with the specified user and port.

Hashing Known Hosts Files

When you connect to an SSH server, the ssh client stores the server's hostname, IP address and host key in a file named known_hosts. It will by default be in your ~/.ssh directory. Having the IP addresses of the servers you connect to regularly in plaintext can be a security risk if you are on a shared host, or your client gets compromised (stolen laptop, for example). An easy way to avoid this problem is to obscure the information in the known_hosts file by hashing it. Hashing your known_hosts file is easy, you just use the ssh-keygen command, giving it the file path.

ssh-keygen -H -f ~/.ssh/known_hosts

While this hashes all existing host keys, any host keys that get added to your known_hosts file after you hash it do not get hashed by default. To make it the default, add the directive HashKnownHosts to your ~/.ssh/config file. Here is an example of hashing a known_hosts file. First, here is what the file looks like beforehand:

dmaxwell@kaylee:~/.ssh$ head known_hosts 10.100.6.151 ssh-rsa AAAAB4NzaC1yc2EAAAABIwAAAIEAuVgRdptT3xsQoGkiNnJb4Zb02p07MaZX02MFs5JhoqmvV5X5Z/LEQH0S7ngSn3b8kQUnocGulJgLchwfThrd/1OkdyOKdpgXxH/rmDXfwh/MZBNBxnMWBa1HpXSc1gxyDfSSxo+VPa1NCP+ob0dWx4sI+JFJ5cVzbQng4rKp3x8= 10.100.6.162 ssh-rsa AAAAB4NzaC1yc2EAAAABIwAAAIEAxpQuMJR4Dq/MmrpUryYlNbP+BIWgJlr0LAfaHTIU64Ho6F58Bb1QzlUeeHQSI9f6qFW9aPsBC3Gd5wgQBUj3byinXXHC/10c3vmb2aEujmyL6en2Pef4AN8bKgaRtJq2G/H4MkPWBzxqZPb/k9c3a26P/DjG4y01TMw9vCld+As= ...

Here we run the ssh-keygen command:

dmaxwell@kaylee:~/.ssh$ ssh-keygen -H -f ~/.ssh/known_hosts /home/dmaxwell/.ssh/known_hosts updated. Original contents retained as /home/dmaxwell/.ssh/known_hosts.old WARNING: /home/dmaxwell/.ssh/known_hosts.old contains unhashed entries Delete this file to ensure privacy of hostnames

And here is what the file looks like afterward (Note that we deleted the backup file when we were done):

dmaxwell@kaylee:~/.ssh$ head known_hosts |1|PdThGCuhg23t9bcURxyitJTmfKk=|/z+Xvh4xPuDni8PTB5iK7KKnGdA= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAuVgRdptT3xsQoGkiNnJb4Zb02p07MaZX02MFs5JhoqmvV5X5Z/LEQH0S7ngSn3b8kQUnocGulJgLchwfThrd/1OkdyOKdpgXxH/rmDXfwh/MZBNBxnMWBa1HpXSc1gxyDfSSxo+VPa1NCP+ob0dWx4sI+JFJ5cVzbQng4rKp3x8= |1|vkLZ22nl30gyJ3gIX74FUF7b3eg=|uy5oSZ8avgZQZE+dwMd/mXGoA38= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAxpQuMJR4Dq/MmrpUryYlNbP+BIWgJlr0LAfaHTIU64Ho6F58Bb1QzlUeeHQSI9f6qFW9aPsBC3Gd5wgQBUj3byinXXHC/10c3vmb2aEujmyL6en2Pef4AN8bKgaRtJq2G/H4MkPWBzxqZPb/k9c3a26P/DjG4y01TMw9vCld+As= ... dmaxwell@kaylee:~/.ssh$ rm known_hosts.old

Donate to OpenSSH!

OpenSSH is an amazing tool, one most system and network admins couldn't live without. I encourage you to donate to the OpenSSH project.

More Information