DevOps ToolChain, WikiPedia, CC BY-SA 4.0
Hardening, as I define it, is the process of applying best practices to make it harder for others to obtain unauthorized access to a given service or to the data transmitted by the service. In this post we’ll take a look at hardening SSH access to our server, as well as making it more difficult for others to potentially snoop our SSH traffic.
We’ll be using a fresh AWS EC2 instance running Ubuntu 16.04 for our examples. If you’re running a virtual server in Azure, Digital Ocean, or some other hosting provider, you’ll want to check out how the equivalent of AWS security groups are configured. And of course, these techniques can also be applied to non-virtual systems.
AWS Security Groups
The first step in hardening your SSH server is applying a more restrictive security group to your instance. Think of AWS security groups as custom firewalls you can apply to your instance. Even better, these custom firewalls can apply source-based filtering rules that only allow traffic from subnets or hosts you specify.
Subnet-based rules provides for rules like “Only allow SSH traffic from my development team’s network 10.90.4.0/24” If your internal network is segregated and configured such that developers must authenticate to receive a 10.90.4.0/24 IP address, an additional safeguard is added.
A host-based rule will only allow traffic from a given host (strictly speaking, a given IP address; if a host is behind a NAT then any hosts also behind that NAT will be allowed). This is what we’ll use.
In the above example, we’ve created a security group
private-ssh-sg and added a single Inbound rule that allows traffic on port 22 from a specific IP address. This will effectively only allow packets whose source IP is specified in that rule to reach port 22 of the instance.
SSH Cipher Strength
$ git clone https://github.com/arthepsy/ssh-audit $ cd ssh-audit $ ./ssh-audit.py your.ip.address.here
Our first pass uncovers a number of issues:
- use of weak elliptic curves in the key exchange algorithms supported
- use of weak elliptic curves in the host-key algorithms supported
ssh-audit goes on to recommend the key exchange, host key, and MAC algorithms to remove.
Let’s look at changes we want to make to our
Protocol 2 HostKey /etc/ssh/ssh_host_ed25519_key KexAlgorithms firstname.lastname@example.org 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
Restart your SSH daemon with
sudo systemctl restart ssh. Note: It’s a good idea to have a second terminal logged in if you bork your SSH configuration and lock yourself out of your instance.
Once we’ve updated our
sshd_config configuration, it’s time to run an audit against it.
Nice! Strong key exchange, encryption, and MAC algorithms all around.
There are different interpretations as to what constitutes two-factor or multi-factor authentication. Many believe that two-factor authentication implies an additional authentication code delivered via text message or provided by a key fob. Others may consider the steps taken to obtain access to a given computing resource as a part of the authentication steps (e.g., to obtain access to a given server you must get past the security guard, provide a retinal scan, and so on). In this example, we’re going to use the former interpretation.
We’ve chosen to use Authy in this example for two-factor authentication using a time-based one time password. To get started, install the Authy application on your phone (iOS or Android) and follow the quick-start prompts.
After you’ve successfully set up the application on your phone, you can download the app to your desktop or add it to Google Chrome.
Getting Your EC2 Instance Ready
To use the authentication code provided by Authy to add an additional authentication step for SSH logins requires installing the
libpam-google-authenticator module and configuring both SSH and PAM.
Install the module with
sudo apt-get install libpam-google-authenticator.
Now, as a user that needs to use two-factor authentication, run
google-authenticator to get set up. The application will generate several prompts, the first of which is
Do you want authentication tokens to be time-based to which you’ll answer “yes”
google-authenticator will then generate QR-code that you can scan with the Authy phone application, as well as a secret key that can be used with the phone, desktop, or browser application. When using the desktop application I prefer just copy-paste of the secret key.
There are additional prompts from the application to follow:
Do you want me to update your "/home/ubuntu/.google_authenticator" file (y/n) y Do you want to disallow multiple uses of the same authentication token? This restricts you to one login about every 30s, but it increases your chances to notice or even prevent man-in-the-middle attacks (y/n) y By default, tokens are good for 30 seconds and in order to compensate for possible time-skew between the client and the server, we allow an extra token before and after the current time. If you experience problems with poor time synchronization, you can increase the window from its default size of 1:30min to about 4min. Do you want to do so (y/n) n If the computer that you are logging into isn't hardened against brute-force login attempts, you can enable rate-limiting for the authentication module. By default, this limits attackers to no more than 3 login attempts every 30s. Do you want to enable rate-limiting (y/n) y
NB: It is a good idea to save your emergency scratch codes in the event you lose access to the devices that are generating your OTPs.
Now that you’ve configured the authenticator, it’s time to update
sshd_config to consult the PAM Google Authenticator module when a user attempts to log in.
/etc/ssh/sshd_config as root, and set the following:
ChallengeResponseAuthentication yes PasswordAuthentication no AuthenticationMethods publickey,keyboard-interactive
sudo systemctl restart ssh.
/etc/pam.d/sshd replace the line
@include common-auth with
auth required pam_google_authenticator.so.
sshd PAM module has been configured in this manner users will be challenged for a two-factor authentication code, so it’s important that every user on the system be configured with the
Test your login!
Try logging in via
ssh with the user you’ve just configured for two-factor authentication. You should receive a prompt requesting a verification code (after your key is authenticated).
Enter the code displayed on your Authy app (note that all of your Authy apps will display the same code for the same application configuration) to login.
One Last Thought on Authy
While writing this post I was doing additional research on two-factor authentication implementations; while Authy supports time-based one time passwords, it supports additional methods that require access to their infrastructure. If you don’t care for providing your cellphone number (and a lot of people don’t), try out Authenticator, a Chrome plugin that doesn’t require any account setup.
You’ll notice if you try out different applications that you can use the same secret key and each application will generate the same code at the same time, hence the time-based one time password.
There is no such thing as perfect security save turning off the computer, disconnecting all of its cables, putting it in a trunk, filling that with cement, and tossing it into the Pacific. Even that might not be perfect. In the absence of perfection we can put as many barriers in place between our server and others that shouldn’t have access to it. In this post we’ve taken a look at several of those barriers:
- source-based firewall rules that only allow access on port 22 from a specific IP or subnet
- hardened key exchange algorithms, ciphers, and MACs for SSH
- two-factor authentication that requires both public key authentication as well as an OTP code
We did not cover additional techniques such as configuring SSH to listen on a different port; I’ve found that despite explaining that this is done primarily to minimize port-scanning chatter (who wants to sift through
fail2ban logs with script kiddie traffic) it never fails to incite the crowd of folks who just learned the phrase security through obscurity to gather up their pitchforks.
If you have any additional recommendations regarding SSH security hardening, please leave a comment!