Most developers make use of SSH servers on a regular basis and it’s quite common to be a bit lazy when it comes to the admin of some of them. However, this can create significant problems because SSH is usually served over a port that’s remotely accessible. I always spend time securing my own SSH servers according to some best practices, and you should review the steps in this article yourself. This blog post will expand upon these best practices by offering some improvements.
Setup SSH Server Configuration
The first step is to make the SSH service accessible via only the local network and Tor. Tor brings a few benefits for an SSH server:
- Nobody knows where users are connecting to the SSH server from.
- Remote scans need to know the hidden service address Tor uses, which reduces the risk of automated scan attacks on known login/password and bugs in the ssh server.
- It’s always accessible remotely, even if the user’s IP address changes; there’s no need to register IP addresses or track changes.
To do so, you’ll need to run a Tor leaf node (if you don’t know how, the Arch Linux wiki has a good article on the subject). Then, add the following lines to the end of the torrc configuration file
Restart Tor, then edit the sshd_config file according to the best practices linked to above. It should include something similar to the following configurations
You will notice a few changes compared to the direction given in the first link. The first change is the addition of UsePrivilegeSeparation sandbox. This further restricts and isolates the process that parses the incoming connection. You might have a different implementation depending on the distro you use, but it’s an improvement anyways.
The second set of changes include adding keyboard-interactive to the AuthenticationMethods and setting ChallengeResponseAuthentication to yes. This adds the use of a One Time Password (OTP) in addition to a public key. One time passwords are a nice addition because they make it so that additional knowledge that changes over time is also needed to login. Many websites do this as an extended mean to protect login information, including GitHub, facebook, Gmail, and Amazon. If you haven’t turned on OTP or 2FA on these sites, you really should do so. Now we’ll configure the SSH server to use this.
In an OTP setup, each user has their own secret key and this key is different for every service. The following setup will be done with pam_oath from the oath-toolkit package. Please install it before continuing. For each user you want to enable OTP for do, the following
$ head -10 /dev/urandom | sha512sum | cut -b 1-30 a0423afcb323d675ba9bbc36d18253
Add the following line in /etc/users.oath for each user
HOTP/T30/6 user - a0423afcb323d675ba9bbc36d18253
The hexadecimal key should be unique and you shouldn’t copy the one in this example! Once you have added all users, make sure nobody can access this file:
# chmod 600 /etc/users.oath # chown root /etc/users.oath
Now, let’s enforce all login to require the OTP code when connecting to SSH by adding the following line at the beginning of /etc/pam.d/sshd
auth sufficient pam_oath.so usersfile=/etc/users.oath window=30 digits=6
With all that is done, you now need to add all the users to the ssh-users group; this needs to be created first with sudo groupadd ssh-users. Then, run the following command
sudo usermod -a -G ssh-users user
Now, you can restart your SSH server and it will require you to input your OTP code. You can get that code by typing the following command :
oathtool -v -d6 a0423afcb323d675ba9bbc36d18253
This will output the following
Hex secret: a0423afcb323d675ba9bbc36d18253 Base32 secret: UBBDV7FTEPLHLOU3XQ3NDAST Digits: 6 Window size: 0 Start counter: 0x0 (0) 621877
The last line is the password you’ll need, and it will change over time. Obviously, this isn’t very nice to do every time you login to your server. Today, most people have a smart phone with a decent level of security on it. Most manufacturers provide full disk encryption and, sometime, even containers like Samsung Knox. So let’s use them for that purpose. I use FreeOTP on Android inside Samsung Knox; it should provide ample security for the task. Now, the easiest method to upload a key to the phone is to use a QR code. Install qrencode and proceed with the following line
qrencode -o user.png 'otpauth://totp/user@machine?secret=UBBDV7FTEPLHLOU3XQ3NDAST'
This should generate a png file of a QR code you can use to quickly and easily create a key for each user. Obviously, this png should be handled carefully as it contains the secret key for your OTP configuration!
Setup SSH Client Configuration
Now it’s time to configure the SSH client you will use to connect to the server; if you also followed the instructions of this article, you should have also started on a secure configuration for your SSH client. If so, you should have something like the following configuration either at the system level in /etc/ssh/ssh_config, or in your user config ~/.ssh/config.
Host github.com KexAlgorithms email@example.com,diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1 Host * KexAlgorithms firstname.lastname@example.org,diffie-hellman-group-exchange-sha256 PubkeyAuthentication yes HostKeyAlgorithms email@example.com,firstname.lastname@example.org,ssh-ed25519,ssh-rsa Ciphers email@example.com,firstname.lastname@example.org,email@example.com,aes256-ctr,aes192-ctr,aes128-ctr MACs firstname.lastname@example.org,email@example.com,firstname.lastname@example.org,email@example.com,hmac-sha2-512,hmac-sha2-256,hmac-ripemd160,firstname.lastname@example.org ServerAliveInterval 300 ServerAliveCountMax 2 TCPKeepAlive yes Compression yes CompressionLevel 9 UseRoaming no Host *.onion ProxyCommand socat - SOCKS4A:localhost:%h:%p,socksport=9050
If you have configured your server to only answer on Tor as described in my previous blog post, you probably want to easily reach it from the various devices you use. Once Tor has been installed on them it’s possible to contact the server at the address given in /var/lib/tor/hidden_service/ssh/hostname. Remembering the random sequence of numbers and letters is not trivial; this is where one last trick in the user config will help. Append the following lines to the client configuration file
Where nice is the name you will remember to join the SSH server and random4name.onion is the real name of the hidden service. If you have not generated public key for your client, it is time to do so with the following comand :
ssh-keygen -t ed25519 -o -a 100 ssh-keygen -t rsa -b 4096 -o -a 100
These commands generate two keys, and you only need to execute the first line; ed25519 key generation is more efficient than a 4,096 bit RSA key while being just as secure. If you have any keys that weren’t generated by one of these commands, you should regenerate them now and update the server authorization keys as soon as possible. Don’t forget to protect them with a long pass-phrase and use ssh-copy-id to deploy them to the server.
You should now be able to fully trust connecting the server to the public Internet. One final note: never use ssh -A to connect to a server because it provides direct access to your private key to any admin on the server without password protection. If you need to use an intermediate host, you should instead use ProxyCommand in your client configuration.