Internet connected services are especially vulnerable to login attacks because anyone in the world can log in to your account from the comfort of their own couch. Several authentication add-ons have been developed over the years to combat this inherent weakness in the standard user/password authentication model. The most common is Two-Factor Authentication (2FA) because 2FA requires you to prove you’re in possession of something in addition to knowing a username and password. Another well known method requires users to be in possession of a hardware device that provides rotating passwords.
In this article, I’ll show you how to lock down your Linux SSH server using both methods – a Yubikey is required to provide the password and a Google Authenticator token is required to complete the login.
A Yubikey is a hardware device that provides various cryptographic authentication mechanisms such as One Time Passwords (OTP) and Public Key Encryption (PKI). Google Authenticator is a software application that provides OTPs for use as a second factor of authentication.
Using Google Authenticator to secure your Linux SSH logins
First thing’s first – download the Google Authenticator mobile app for your phone or tablet. Once you have that, we can get to work. It is available from both the Play Store and the App Store.
Linux login authentication is usually provided by the Pluggable Authentication Modules (PAM) libraries. As the name indicates, PAM supports the addition of arbitrary authentication modules. In this case, we’re going to “plug in” the Google Authenticator layer to the normal SSH login to prompt for a code after a user provides their password.
To do that we need to start by installing the necessary PAM libraries:
$ sudo apt-get install libpam-google-authenticator
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following extra packages will be installed:
libqrencode3
The following NEW packages will be installed:
libpam-google-authenticator libqrencode3
0 upgraded, 2 newly installed, 0 to remove and 356 not upgraded.
Need to get 46.4 kB of archives.
After this operation, 208 kB of additional disk space will be used.
Do you want to continue [Y/n]? y
And run the authenticator for the first time:
$ google-authenticator
Copy these codes somewhere safe. If you ever lose your phone or have some other issue where you cannot get a code from it, you will need one of these emergency scratch codes to log in.
Now we need to set up this login into your Google Authenticator app. To do this, press the + sign in your Google Authenticator app and then decide whether you want to scan the QR code or type in the secret key. Either method will work. Once you’ve added it, there will be a new entry in your Google Authenticator app that looks something like this:
Now answer yes
to the Do you want me to update your ~/.google_authenticator file (y/n) question, which will prompt you with more questions.
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
You can choose answers that make the most sense for you. I choose to disallow multiple token uses and to enable rate-limiting. I opted against allowing a four minute time skew because both my computer and my phone update their time periodically so it’s hard to imagine how the clock could become so grievously out of sync. If you’re intending to log in to a remote system with a lot of latency, you may wish to allow a longer time skew.
Out of curiosity, if we take a look at our new .google-authenticator file, we can see those choices saved there, along with our emergency tokens.
$ cat .google_authenticator
NO7ZW33I34JMPCKB
RATE_LIMIT 3 30
WINDOW_SIZE 17
DISALLOW_REUSE
TOTP_AUTH
73444347
15364641
36772087
14155810
92578001
It’s a good idea to ensure this file is only readable by your user, which is how it is set up by default:
$ ls -l .google_authenticator
-r-------- 1 jdw jdw 126 Apr 26 08:30 .google_authenticator
We now have Google Authenticator set up, but our system has no idea that it’s supposed to use it for login yet. To do that, we need to update the Pluggable Authentication Module (PAM) configuration.
Edit the PAM ssh config file as root:
$ sudo vim /etc/pam.d/sshd
Add this line somewhere, I put it at the bottom:
# Use Google Auth for ssh logins
auth required pam_google_authenticator.so
One more change is needed; modify your challenge and response setting in your sshd_config
file by switching no
to yes
:
$ sudo vim /etc/ssh/sshd_config
# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
ChallengeResponseAuthentication yes
Now restart your SSH daemon and try it out:
$ sudo service ssh restart
Don’t log out. Leave yourself logged in just in case you can’t get back in for some reason. Start another terminal and SSH into your newly configured machine.
Once I enter my password, I am prompted for a current Google Authenticator code. Entering both properly allows you to log in:
$ ssh jdw@192.168.1.118
Password: <=== typed in my password
Verification code:
Welcome to Ubuntu 12.04.5 LTS (GNU/Linux 3.13.0-32-generic x86_64)
Using a Yubikey to secure your Linux SSH logins
We now have a system that requires a username, a password, and a Google Authenticator token to log in. That’s pretty secure, but we can do even better. We can configure the system to only accept a randomly generated password from a Yubikey.
Out of the three pieces of data needed to log in (username, password, authentication code), nobody could possibly know the last two in advance, or reuse them if discovered.
First thing’s first – get a Yubikey either direct from Yubico or from a place like Amazon. I will be using a Yubikey Nano for this.
Next, let’s install the necessary packages. Add the Yubico PPA (Personal Product Archive) and install the libpam-yubico
PAM library.
$ sudo add-apt-repository ppa:yubico/stable
$ sudo apt-get update
$ sudo apt-get install libpam-yubico
The library file itself is named pam_yubico.so
and should be installed into /lib/security/
$ ls -l /lib/security/
total 104
-rw-r--r-- 1 root root 10296 Mar 19 2013 pam_ck_connector.so
-rw-r--r-- 1 root root 43480 Mar 28 2013 pam_gnome_keyring.so
-rw-r--r-- 1 root root 47672 Nov 25 2016 pam_yubico.so
$
We’re going to need a set of Yubi API credentials. Visit the Yubico API page here.
And follow the instructions. Once you’ve supplied your email and your Yubikey OTP, the site will give you a Client ID and a Secret Key you can use:
The last bit of information you’ll need is your Yubikey token. That is just the first 12 digits of any one-time password (OTP) that your Yubikey spits out. To get this, focus your cursor into a terminal window or a text editor – some place that will take input. Then insert your Yubikey into your USB drive. If it has a button, press it. If it does not, such as the Nano model, then just lightly press the Yubikey itself into the USB port instead. It should write out a one-time password in the form of a long string of characters. The first twelve digits are all we need for this part. In my case, that is this:
cccccchcdjed
Next, configure the /etc/pam.d/sshd
file to require the yubikey to log in by adding this line to the top of the file, using the id
and key
you received from the Yubi API site:
auth required pam_yubico.so id=38399 key=lZqKSrHhyQ6dEBZnIEe2+Uwe3NA= debug authfile=/etc/yubikey_mappings mode=client
We also have to tell the sshd daemon that it should not accept passwords anymore If we don’t, then it will still accept normal passwords in addition to Yubikey OTPs, which isn’t really what we want. To do that, comment out this line by putting a #
in front of it:
#@include common-auth
Now we need to associate your user with your Yubikey. To do that, edit the /etc/yubikey_mappings
file and add your username and your 12-character Yubikey token separated by a colon:
jdw:cccccchcdjed
If you have multiple Yubikeys, you can add more by just appending the additional Yubikey tokens to the same line, separated by a colon. If I had three Yubikeys, my line would look something like this:
jdw:cccccchcdjed:joewubtklruy:sgjyirtvskhg
Finally, restart your ssh session and try it out. Note that you should stay logged in to your working ssh session just in case this does not work.
$ sudo ssh restart
When prompted for my password, I press my Yubikey instead and it logs me in. Because I’ve included the debug
option in the etc/pam.d/sshd
file, I get a lot of output, but I’ve pared that down for this snippet because its really only useful if something has gone wrong:
$ ssh 192.168.1.118
Password: <=== came from pressing my Yubikey
Verification code:
Welcome to Ubuntu 12.04.5 LTS (GNU/Linux 3.13.0-32-generic x86_64)
That’s all there is to it. Both of these technologies are very technical in nature, but the heavy lifting is done by the PAM libraries supplied by the individual vendors. That means we’re able to secure ssh access to our Linux systems with relatively few configuration changes.
Backgrounder on SMS and 2FA
While not necessary to complete this tutorial, some background into why SMS 2FA is weak can be helpful. There’s a reason why things like Yubikeys and authenticator apps are preferable over SMS 2FA.
SMS two-factor authentication is weak
The most widely accepted definition of 2FA is “something you know and something you have”. The “something you know” is a password. The “something you have” usually means a six-digit code that you provide from some device that only you have access to. Providing the correct code is enough proof that you “have” that something.
The most common form of 2FA on the internet today is the use of SMS text messaging to send 2FA codes at login time. Due to weaknesses in the SS7 protocol and the poor customer verification within mobile support teams, it is fairly easy to redirect a text message to any phone you want. Two weaknesses are routinely exploited in the SMS 2FA method: First, a phone number is not permanently tied to a phone, so anyone can receive the SMS message with the code. Second, the SMS validation code is sent to you through an untrusted and unencrypted medium. By its very definition, this does not fulfill the requirement of being “something you have”. It’s actually “something I just gave you,” which is not the same.
Authenticator apps and hardware tokens are stronger
A better solution is to use a mobile authenticator app such as Google Authenticator or Authy. An even better solution is to couple that type of 2FA with a rotating, unpredictable password from a device like a Yubikey. Neither apps nor hardware tokens transmit any data over the internet or cellular system, which removes that interception vector. The downside is that they are harder to set up, which means the weaker SMS method is used most often.
Related:
Encryption Resources: A Big List of Tools and Guides
What is a brute force attack?
The jargon-free guide to computer and internet security
The ultimate guide to desktop Linux security