More locks for your SSH door

Additional ways to harden SSH access

Security isn't an exact science, so the more difficulties you can put in a hacker's way, the better. This article considers how to enhance Secure Shell (SSH) access by eliminating passwords and using public/private key pairs instead. The article also explores how to recognize and block possible attacks, including brute-force and dictionary attacks, by denying server access to origins that are identified as unsafe.

Share:

Federico Kereki, Systems Engineer, Freelance

Photo of Federico KerekiFederico Kereki is a Uruguayan systems engineer with more than 20 years of experience developing systems, doing consulting work, and teaching at universities. He has been working with open source software for over 10 years and particularly appreciates the greater security of Linux. He recently wrote Essential GWT, a book about this open source tool.


developerWorks Contributing author
        level

27 September 2011

Also available in Spanish

My first article about hardening SSH access (see Resources) considered three methods that are suitable for small operations, such as a home server or a small business with few people requiring remote SSH access:

  1. Changing SSH's standard port to an unusual value and reinforcing SSH configuration so that simple-minded attacks just bounce back.
  2. Defining a restricted list of users who are allowed to log in using Pluggable Authentication Modules (PAM).
  3. Completely hiding the fact that you even allow SSH access and requiring a special "knock" sequence to be recognized as a possible user.

Using PAM to define a restricted list of users who are allowed to log in still makes sense for larger setups, but the other two options can become bothersome. Therefore, this article examines a couple methods of enhancing security that you can more easily apply to larger configurations:

  • Password-less connections
  • Rejecting attackers

Password-less connections

Passwords are not secure against social engineering methods for a variety of reasons—too many users write their passwords down so they won't forget them, "over the shoulder" looks (the reason most ATMs have some kind of cover for your hand), keyboard sniffers, and more. Moreover, if you use passwords that aren't complex, brute-force attacks or dictionary attacks might be able to retrieve them and compromise your site.

Using PuTTY with public/private keys

The well-known PuTTY terminal program can use public/private key pairs, but you have to convert them somewhat because the program requires a specific file format. After you generate your key pair as shown in the text, use this command: puttygen path.to.your.private.key -o path.to.your.putty.key. Then, open PuTTY and select Connection > SSH > Auth to pick your newly generated private key file. After you do this, you can use your key pair for password-less logins via PuTTY.

By using public/private key logins, you can manage without passwords. The public part of your key pair resides at the server, and you keep your private key safe at your remote machine or machines. You can also protect your private key with a passphrase for extra safety, as you'll see in this article. Other people cannot log in as you unless they obtain your private key because, at least currently, it's not computationally feasible to derive one key from the other. See Resources for more information about RSA public/private keys.

First, working as root, make sure that your sshd configuration allows for private key logins. The /etc/ssh/sshd_config configuration file should include the RSAAuthentication yes and PubkeyAuthentication yes lines as shown, not commented out. If that isn't the case, add or modify the lines and restart the service with /etc/init.d/sshd reload. Then, use ssh-keygen to create a public/private key pair. You need a pair for every user who will be able to perform remote password-less logins. See Listing 1 below for an example.

Listing 1. Use ssh-keygen to generate a public/private key pair
$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/fkereki/.ssh/id_rsa): /home/fkereki/mykey
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/fkereki/mykey.
Your public key has been saved in /home/fkereki/mykey.pub.
The key fingerprint is:
6c:36:09:fc:7f:63:27:ff:0b:cd:7c:7f:a9:e9:60:ec fkereki@fkereki-desktop
The key's random art image is:
+--[ RSA 2048]----+
|                 |
|     .           |
|      o          |
|       + .       |
|        S        |
|       o o.   +  |
|          .+=..+o|
|          oo.=o.+|
|           E.+oo=|
+-----------------+

Creating the key pair is only the first step. You have to copy the public part to the server you want to connect to. Several online sites recommend that you directly edit certain files to accomplish this, but using the ssh-copy-id command is much easier. See Listing 2 below as an example.

Listing 2. Use ssh-copy-id to send your public key to the remote server
$ ssh-copy-id -i /home/fkereki/mykey.pub fkereki@some.server.com
The authenticity of host 'some.server.com (123.45.67.89)' can't be established.
RSA key fingerprint is 16:a4:d8:6a:ee:e0:8d:f4:72:a8:af:42:75:1d:28:3b.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'some.server.com,123.45.67.89' (RSA) to the list of known
hosts.
fkereki@some.server.com's password:

Testing your password-less connection is now easy: ssh remote_user@remote_host is enough. If you used a passphrase when creating the key pair (and you should have, as I'll explain shortly) you have to type it in. If everything was done correctly, you establish an SSH connection to the remote server without having to enter your password for that machine. See Listing 3 below for an example.

Listing 3. Use ssh to give your password-less login a try
$ ssh fkereki@some.server.com
Enter passphrase for key '/home/fkereki/mykey':
Last login: Mon Jan 10 18:40:11 2011
$ logout
Connection to some.server.com closed.

Remembering your passwords

For users who prefer a graphical user interface (GUI), you may want to look at the keychain command. The keychain command allows you to resume an ssh-agent between logins, even after a logout. You may have to use your distribution's package manager to install this command (it isn't usually included by default). To use it, enter keychain path.to.your.private.key from the command line, enter your passphrase, and you're set. Your passphrase is stored until you reboot the server, and you won't be required to re-enter it. If you want to clear all keys in the cache, use keychain --clear or keychain -k all to stop the program entirely. GNU Network Object Model Environment (GNOME) users might want to look at the similar Seahorse application (see Resources).

Now you can see why using a passphrase is better. If you don't use one when you create a public/private key pair, anybody who gets access to your machine—and thus to your private key—can immediately gain access to all the remote servers you can log in to. Using a passphrase adds another level of security because you can take your private key with you on a Universal Serial Bus (USB) stick and use it from any machine to log in to your remote servers. Without a passphrase, losing your stick would automatically compromise all of your servers.

Of course, having to enter a passphrase each and every time you log in will eventually become a bother and is quite inconvenient for unattended processes, such as a daily automatic backup with rsync. There is a solution to this. Use ssh-agent at the beginning of your session and ssh-add for each of your passphrases, and then your passphrases are automatically entered for you. See Listing 4 below for an example. You can use ssh-agent -k to stop your current session. After that, you'll have to enter your passphrase again for every remote login.

Listing 4. Using ssh-agent spares you from re-entering your passphrase for every login
$ ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-Rvhhx30943/agent.30943; export SSH_AUTH_SOCK;
SSH_AGENT_PID=30944; export SSH_AGENT_PID;
echo Agent pid 30944;

$ ssh-add
Enter passphrase for /home/fkereki/mykey:
Identity added: /home/fkereki/mykey (/home/fkereki/mykey)

$ ssh fkereki@some.server.com
Last login: Mon Jan 10 18:44:15 2011 from 192.168.1.108

Rejecting attackers

Even if you implement all the security measures discussed so far in both this article and the preceding one, you can bet that there will still be hackers trying to gain access to your servers using brute-force methods, if necessary. But there's a simple solution for that. Think about what happens if you enter your password incorrectly enough times. You are locked out for a certain amount of time during which you aren't able to attempt a new login. You apply this technique to would-be hackers by modifying the system so that it rejects connections from the hackers' IPs. Of course, you don't necessarily want to enforce this measure forever, so after a judicious time you go back to accepting connections again from those IPs. However, if the hackers begin anew with their tricks, the IPs are banned again, thus making it almost impossible for them to get into your server.

There are many tools for this, such as DenyHosts, Fail2ban, BlockHosts and more (see Resources). This article focuses on DenyHosts. That said, if you want to protect services other than SSH, the other programs might be better suited. Please note that all these packages are considered stable and haven't had new releases for two years or more. So, don't think they are obsolete or abandoned, and don't let this lack of updates discourage you from using them.

DenyHosts periodically scans the authentication logs at your server looking for recent unsuccessful login attempts. Whenever the number of attempts coming from the same IP reaches a certain threshold, DenyHosts reacts by adding a line, such as sshd:111.222.33.44 to the /etc/hosts.deny file. This blocks the potential brute-force attack. You can configure DenyHosts to notify you of any blocked IPs via e-mail. After a specified time passes, DenyHosts uses a similar method to allow login attempts from the original IP, unless that IP has been blocked too many times, in which case the ban is final.

DenyHosts is written in Python. Thus, the only requirement is that you have Python version 2.3 or later available. Though you can get binary packages for some distributions, installation from source is quite simple. Get the package (see Resources) and follow the short instructions shown in Listing 5 to prepare the application for configuration.

Listing 5. Installation of DenyHosts from source
$ tar zxvf DenyHosts-2.6.tar.gz
$ cd DenyHosts-2.6
$ su
$ python setup.py install
$ cd /usr/share/denyhosts
$ cp denyhosts.cfg-dist denyhosts.cfg
# ... now edit denyhosts.cfg ...

The suggested denyhosts.cfg-dist file has some reasonable values, but you should edit it to suit your environment. You will most likely want to look at the parameters described in Table 1 below. There are even more parameters, but these are the ones you probably want to modify first.

Table 1. Configuration parameters to modify for DenyHosts
ParameterDescription
ADMIN_EMAILYour e-mail address. If present, this parameter enables you to receive e-mail messages whenever DenyHosts decides to block a host. If you set this parameter, also modify SMTP_HOST and SMTP_PORT to specify your e-mail server and to check the other SMTP_ parameters.
AGE_RESET_VALIDSimilar to PURGE_DENY (see below) it defines a time lapse after which, if there are no further failed attempts, the failures count for a host are reset to zero. AGE_RESET_INVALID is similar but applies to (failed) non-existing user login attempts, while AGE_RESET_ROOT pertains to failed root login attempts. You should also set RESET_ON_SUCCESS to define whether the count of invalid attempts for an IP is or isn't reset to zero after a successful login. The default is no, but I prefer yes because if you sometimes mistype your password, despite having being able to login several times, you'll eventually be blocked.
DAEMON_PURGEDefines how often DenyHosts checks if there are hosts that should be unblocked. This value can be much higher than the time between runs, for instance 2h (every two hours) or 1d (once a day).
DAEMON_SLEEPSpecifies how long DenyHosts waits between successive checks of SECURE_LOG. A reasonable value could be 30s (thirty seconds), for example. You don't want to run the script too often (wasting processor cycles) or too seldom (giving hackers complete freedom).
DENY_THRESHOLD_VALIDDefines how many login attempts (for valid, non-root, users) will be tolerated before the host is blocked. There's also a DENY_THRESHOLD_ROOT value that applies exclusively to root login attempts, but you're better off directly forbidding root to remotely connect through SSH to your server, as shown in the previous article. Another value, DENY_THRESHOLD_INVALID, applies to login attempts for nonexistent users. The latter value should be smaller than the one for valid users because this kind of login attempt is more likely to be a part of a phishing attempt.
HOSTS_DENYMust point to the file with restricted host access data, which is usually /etc/hosts.deny. This parameter is mandatory. If it's wrong, DenyHosts won't work as expected.
LOCK_FILEThe path and name of a file that, if it exists when you run DenyHosts, causes it to exit immediately (for instance, /tmp/denyhosts.lock). The file is similar to /etc/nologin, which causes login attempts to fail. If the file doesn't exist when you run DenyHosts, it will be created and then deleted upon exit to avoid having two instances of the program running at the same time.
PLUGIN_DENYPoints to an application you want to run whenever a host is blocked. The application is invoked with the host as an argument. You should use this whenever you want to take extra actions, such as updating a database. Similarly, the application referred by PLUGIN_PURGE is called when a host is removed from the HOSTS_DENY file.
PURGE_DENYDefines the time after which a blocked host is allowed again. If empty, hosts are blocked forever. This entry can be in minutes (120m), hours (2h), days (14d), weeks (2w), or years (1y).
PURGE_THRESHOLDDefines how many times a host may get unblocked before it stays blocked forever. If zero, hosts can be blocked and unblocked an infinite number of times.
SECURE_LOGThe path and name of the file where sshd logs its messages. This varies from distribution to distribution. For example it's /var/log/messages for openSUSE®, but other distributions use /var/log/secure or /var/log/auth.log. Also, remember to configure sshd so it produces logs. If this parameter is wrong, DenyHosts simply won't work.
WORK_DIRThe path for DenyHosts' own files, which is usually /usr/share/denyhosts/data. If the path doesn't exist, it will be created.

Now, set DenyHosts to run. Though you can run it from the command line (and even from a cron file, if you wish) the preferred method is in daemon mode by setting it as a service to start running at boot time. See Listing 6 below for the required steps.

Listing 6. You can configure DenyHosts to run at boot
$ su
$ cd /usr/share/denyhosts
... edit daemon-control-dist (see below) and then ...
$ cp daemon-control-dist /etc/init.d/denyhosts
$ chown root.root /etc/init.d/denyhosts
$ chmod 700 /etc/init.d/denyhosts
$ chkconfig denyhosts on
$ /etc/init.d/denyhosts start
... or ...
$ /sbin/service denyhosts start

You have to edit three parameters at the beginning of the daemon-control-dist script so DenyHosts knows where to find its files. Those parameters are described below in Table 2.

Table 2. Parameters to edit in the daemon-control-dist script
ParameterDescription
DENYHOSTS_BINPoints to the denyhosts.py script.
DENYHOSTS_LOCKPoints to the lock file.
DENYHOSTS_CFGPoints to the configuration file, which is /usr/share/denyhosts/denyhosts.cfg for the example in this article.

You are set. From now on, DenyHosts will regularly check your sshd logs, identify possible attacks, and block the originating IPs accordingly.

Conclusion

This article covered more methods for hardening SSH access to your server. Using public/private keys with passphrases to protect them is the safest way to allow access to a server. Having your server recognize possible attacks and locking would-be hackers before they can even try to guess a password also enhances your security. Of course, as always in the security field, do not believe that any number of safe practices can actually guarantee that your server won't get hacked into, but the extra hindrances for attackers are mandatory.

Resources

Learn

  • Check out the previous article, "Three locks for your SSH door" (developerWorks, August 2010) for more methods to secure access to your server.
  • Check the "Using rsync without a password" sidebar in "The rsync family" (developerWorks, April 2009) for an application of certificates to password-less data synchronization.
  • For more information about the RSA algorithm, which is used to generate public/private key pairs in this article, check the original article by the RSA creators and the current standard for it.
  • The AIX and UNIX developerWorks zone provides a wealth of information relating to all aspects of AIX systems administration and expanding your UNIX skills.

Get products and technologies

  • For protecting SSH access against brute-force hackers, check DenyHosts.
  • If you want to protect other services from attackers, Fail2ban (basically any service) or BlockHosts (SSH and File Transfer Protocol, or FTP) could suit you.
  • Try out IBM software for free. Download a trial version, log into an online trial, work with a product in a sandbox environment, or access it through the cloud. Choose from over 100 IBM product trials.
  • Read more about Keychain and Seahorse, for simplifying your user experience with stored private keys and KDE or Gnome.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into AIX and Unix on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=AIX and UNIX
ArticleID=760961
ArticleTitle=More locks for your SSH door
publish-date=09272011