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:
- Changing SSH's standard port to an unusual value and reinforcing SSH configuration so that simple-minded attacks just bounce back.
- Defining a restricted list of users who are allowed to log in using Pluggable Authentication Modules (PAM).
- 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
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.
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 email@example.com The authenticity of host 'some.server.com (126.96.36.199)' 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,188.8.131.52' (RSA) to the list of known hosts. firstname.lastname@example.org'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
give your password-less login a try
$ ssh email@example.com Enter passphrase for key '/home/fkereki/mykey': Last login: Mon Jan 10 18:40:11 2011 $ logout Connection to some.server.com closed.
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 firstname.lastname@example.org Last login: Mon Jan 10 18:44:15 2011 from 192.168.1.108
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
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
|Your 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 |
|Similar 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. |
|Defines 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).|
|Specifies 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).|
|Defines how many login attempts (for valid, non-root, users) will be tolerated
before the host is blocked. There's also a |
|Must 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.|
|The 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.|
|Points 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 |
|Defines 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).|
|Defines 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.|
|The 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.|
|The 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
|Points to the denyhosts.py script.|
|Points to the lock file.|
|Points 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.
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.
- 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.
- Follow developerWorks on Twitter.
- Participate in developerWorks blogs and get involved in the developerWorks community.
- Get involved in the My developerWorks community.
- Participate in the AIX and UNIX® forums: