Securing OpenSSH
If you are a systems administrator of one or more Linux/UNIX systems, OpenSSH is likely one of the most important tools in your toolbox. OpenSSH can be used interactively or embedded in scripts to provide secure remote access and file transfer between systems. But, alas, danger lies ahead. With OpenSSH, it is perilously easy to create sloppy trust relationships or, worse, leave yourself open to common automated attacks.
One of the basic tenets of system security has always been to run only the minimum required services and limit their access only to those who need it. Linux systems make this pretty easy to do, and like most things in the *nix world, you can do it in a number of ways. If you're still reading this article, we assume you need to run OpenSSH. Let's work on limiting access.
On most Linux distributions, you have the choice of handling this at the kernel firewall level or using TCP Wrappers (/etc/hosts.{allow|deny} files).
A simple iptables firewall rule to limit OpenSSH only to your local subnet could be:
iptables -A INPUT -s ! 192.168.0.0/255.255.0.0 ↪-p tcp -m tcp --dport 22 -j REJECT --reject-with ↪icmp-port-unreachable
This tells the kernel to reject any TCP/IP packets not coming from a specific subnet aimed at port 22. (Substitute your own numbers as needed.) In all likelihood, you will have additional iptables rules to protect other applications on this system, so integrate the above into your overall firewall design.
TCP Wrappers is a common feature on most Linux distributions, and OpenSSH has built-in support. To implement the same rule above using TCP Wrappers, add the following line to your /etc/hosts.deny file:
sshd: ALL EXCEPT 192.168.0.0/255.255.0.0
For most situations, it is very unlikely that OpenSSH needs to be dangling out for the world-wild Internet to poke at. Further, it is unlikely that OpenSSH even needs to be available to your entire organization. I highly recommend that access be limited, using either of the above methods, to the smallest audience possible. One common method is to designate one or more systems specifically for the purpose of centralized administration. Configure the rest of your systems to accept OpenSSH connections only from these dedicated and highly secured systems.
To test whether you have limited access to OpenSSH successfully, you can try to connect using an ssh client, or simply connect to the system using a command-line telnet client, for example:
$ telnet mylinuxbox 22
where mylinuxbox is the hostname, and 22 is the port.
If OpenSSH is responding and open, you should see something like the following:
SSH-1.99-OpenSSH_3.9p1
If it is successfully blocked, the connection will be refused. Try this from multiple locations, and confirm that connections are blocked as you expect.
Over the years, OpenSSH has had a few remotely exploitable vulnerabilities due to bugs. These always have been fixed quickly, with updated versions distributed promptly. Unfortunately, a lot of networked systems are not updated. Also, many systems simply are poorly secured. Standard accounts with simple or blank passwords are depressingly commonplace.
All of these are tantalizing targets for crackers and worm authors. A number of malicious worms exist that scan for any SSH servers and then try common vulnerabilities and passwords. If you must have your OpenSSH port open to the world, consider changing its TCP port number. This can be accomplished by changing the Port directive in the sshd_config file, which is commonly in /etc/ssh/. Although any live person trying to crack your systems would likely have little difficulty detecting this, it is usually enough to escape the attention of large-scale automated attacks and worms.
Also, you might consider rebuilding your OpenSSH from sources after modifying the version string. This also can help confuse automated attacks, because many vulnerabilities are dependent on specific versions of OpenSSH. This is done by modifying the version.h file in the OpenSSH distribution and recompiling. Change the SSH_VERSION and SSH_PORTABLE #define lines to anything you like, such as:
#define SSH_VERSION "MyCompany_Vers00001" #define SSH_PORTABLE "foo"
After changing the sources, build and install. This changes the version banner shown in the above telnet test.
Changing the port and version string does nothing to improve security; they simply reduce the risk of being the victim of a common exploit or worm. There is no substitute for securing accounts and making sure your systems are running the latest patch levels of software. If you are using Linux, use a current distribution. Use automated update tools like yum, apt or up2date to keep your system running the latest version of all software and libraries including OpenSSH.
You can confirm that users are selecting good passwords by using a cracking tool like John the Ripper (see the on-line Resources). John the Ripper (JTR) uses the one-way encrypted passwords in /etc/shadow to try to crack passwords. Generally speaking, easier passwords cracked by JTR are more likely to show up in a brute-force dictionary attack commonly used by some worms. JTR supports a wide range of input file formats for those using LDAP, NIS, AFS Kerberos and other authentication services.
If possible, stop using passwords for remote logins. You can accomplish this by setting up trust relationships using public key authentication. These trust relationships can be set up between any two user accounts, on the same system or across the network.
The first step to setting up one of these relationships is to create a private and public key, known as a key pair. Each key is stored in a separate file. By convention, the filenames are the same, but the public key file has an extra .pub extension. The private key stays secret and usually remains in the home directory of the account that will initiate the connection.
OpenSSH will refuse to use any private key file with lax file permissions. To form the trust relationship, the public key is appended to the ~/.ssh/authorized_keys file (usual location on most Linux builds of OpenSSH) on the destination side of the trust relationship. The authorized_keys file contains all the public keys trusted by the owner's account. These keys can be secured further by providing a passphrase, which is used to encrypt the private key. If a passphrase is specified, it needs to be provided before the private key can be used to make a connection. In the example below, we create a trust relationship between two accounts, account1 on a system named localbox and account2 on a system named remotebox.
Create account1's key pair (we will specify a passphrase when asked). The passphrase is not actually displayed as you type it, but it is shown below for demonstration. We specify that the pair should be based on a 2048-bit private key (the default is 1024, which recently has been proven to be much easier to crack):
[account1@localbox account1]$ ssh-keygen -t rsa -b 2048 Generating public/private rsa key pair. Enter file in which to save the key (/home/account1/.ssh/id_rsa): <ENTER> Created directory '/home/account1/.ssh'. Enter passphrase (empty for no passphrase): This%%%Is%%%My%%%Passphrase<ENTER> Enter same passphrase again: This%%%Is%%%My%%%Passphrase<ENTER> Your identification has been saved in /home/account1/.ssh/id_rsa. Your public key has been saved in /home/account1/.ssh/id_rsa.pub. The key fingerprint is: 63:9f:75:d5:80:93:67:5e:d8:69:5a:69:69:4f:a0:76 account1@localbox [account1@localbox account1]$ cat /home/account1/.ssh/id_rsa -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,9668032A1B3D23DA 2IKH5Xt+LR2/nftd8aPi1KPZ4Fn20upJKU1NUAK82HXBb59TPyzmr/05i56ZiPhO UHJCScF+G/k44zheUnyWu+/65iVuzTaa+6q4eYaTna8ElA+PetmfwQF08L780NkU +2VJeEyizXcro9J6oYYGhS+qWfHalmbX6w6IWuAh6jjEhCn+LTlZs1yjGFu5NHDr sWSMNgounPS/ay8ZlNIV+iQB5l/v+bNWiC92ICYbZW0C+o2XKpNvACdUDpA/gfqk b20utNOzBfGAGgmU2EJ5HGq8YQMCjVk0ZN/NVjGqUuACQmW0nB5jYrGE/ka/20xN X4jZWxXGxNeS0PMdxlB5XXXXXXBMwCWx0AZ0SVYdoSbapZJKMsOdEolFU4IFEewm NlgFXfe7vsdFseFsaSdfEkCHaHaMadeYouLooktoqz+mfkbwDpC8msnKcuRJShR9 09rZnoKW/ddvrJv5oIQYFqu7LYJr5ygpW3T3oiFgzsIg//1hAW4h0sTCBWUIGSkk IWTxKFooQ3ABarsIzCHqPBazXnSJ5IVXXXXlglmH9ce5Z6YMNoUdfWBn4Mgc2gNB xCk4KINXMJ7MJsmLxkwuABqcTq4SBCiipr6PLL5G2BKK6ljXLdJe4olDdb/GtxlH mOXNuAWLD1eE5FfAOUSQLGDkeUThisIsNotARealPrivateKeycOBrhdykHI5Sh7 /hVyTVr9t6r6lRBEsMi1T+P2uBAuahNGCyDs+5F1P14y2RJ5GAlDVdM8bXOfnfsO pHiXerStpKw6j85wxoKryzSAGs2eWXtrQKk8IMkDQug= -----END RSA PRIVATE KEY----- [account1@localbox account1]$ cat /home/account1/.ssh/id_rsa.pub ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA54Q3QZLkpRaNN83WUMBGBE+/xZ88ud +SlM74awZa8P6H3TNcEnrb3Lti58bsgjl3z0W5DXWvNRgszoQeBJYNVHlsVIUCp0Vj Vr7aDUn0PiUVI2y658XBlbup5+isgMNuyB7BskVKs9aWYQNCbJVoNo+3wcvA9x+De5 Z7yBapi6E= account1@localbox
This created the two key files /home/account1/.ssh/id_rsa and /home/account1/.ssh/id_rsa.pub. Now, we copy the public key to account2@remotebox. You can do this with any method you find convenient. We use scp to copy the file, and then use cat to append to it:
[account1@localbox account1]$ scp /home/account1/.ssh/id_rsa.pub ↪account2@remotebox:./account1.id_rsa.pub account2@remotebox's password: id_rsa.pub 100% 243 538.1KB/s 00:00
Now we append account1's public key to account2's authorized_key file and set proper permissions on files:
[account2@remotebox account2]$ mkdir ~/.ssh [account2@remotebox account2]$ chmod 700 ~/.ssh [account2@remotebox account2]$ cat account1.id_rsa.pub >> ↪~/.ssh/authorized_keys [account2@remotebox account2]$ chmod 600 ~/.ssh/authorized_keys [account2@remotebox account2]$ rm account1.id_rsa.pub [account2@remotebox account2]$ ls -l ~/.ssh/ -rw------- 1 account2 account2 243 Dec 9 09:42 authorized_keys
By placing the public key on the destination account2, we prove the identity of account1 when it tries to connect. As you can see below, we are prompted for the passphrase when we make the connection:
[account1@localbox account1]$ ssh account2@remotebox Enter passphrase for key '/home/account1/.ssh/id_rsa': This%%%Is%%%My%%%Passphrase<ENTER> [account2@remotebox account2]$
After the basic trust relationship is set up, you can disable password authentication by locking the password using the passwd command and the -l option.
You can restrict this trust further by adding options to the authorized_keys file. In the example below, we include the from= option to add host-based checking, so the private key will be accepted only from predefined network addresses. We also add the command= option, which specifies the one and only command that will be executed after authentication to the remote account. This feature is most useful from inside scripts to create special-purpose trust relationships to automate tasks between systems or accounts:
[account2@kettle account2]$ cat .ssh/authorized_keys from="localbox",command="/bin/df -k" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA54Q3QZLkpRaNN83WUMBGBE+ /xZ88ud+SlM74awZa8P6H3TNcEnrb3Lti58bsgjl3z0W5DXWvNRgszo QeBJYNVHlsVIUCp0VjVr7aDUn0PiUVI2y658XBlbup5+isgMNuyB7Bs kVKs9aWYQNCbJVoNo+3wcvA9x+De5Z7yBapi6E= account1@localbox [account1@localbox account1]$ ssh account2@remotebox Enter passphrase for key '/home/account1/.ssh/id_rsa': This%%%Is%%%My%%%Passphrase<ENTER> 10:32:15 up 8 days, 15:13, 16 users, load average: 0.15, 0.12, 0.09 Connection to remotebox closed. [account1@localbox account1]$
After the uptime command is executed, we are immediately dropped back to account1. This relationship is now locked in to performing only one operation. This could be a single command, script or a restricted shell. The command= option can be used to allow certain users to perform selected elevated privilege operations in a controlled manner. Similar functionality can be gained using tools like sudo, but OpenSSH allows these operations over the network, which is useful in some circumstances. If you have problems with any of these examples, you can run ssh with debugging turned on, using the -vvv command-line option. This usually helps in figuring out what is going wrong; most commonly, file permissions cause OpenSSH to ignore key files.
To facilitate the use of public key authentication in scripts, you can create a key pair for each remote operation and then specify which one to use by invoking ssh with the -i option and the filename of the private key.
For interactive sessions, you can use a program called ssh-agent to cache your decrypted private keys in memory, so you need to provide the passphrase only once. On some Linux distributions, ssh-agent is already hooked into the login process to start automatically. If so, all you have to do is use ssh-add to cache your private keys. If you are interested in ssh-agent, take a look at the ssh-agent and ssh-add man pages for detailed explanation of their uses.
One final comment about public key authentication: with the price and ease of use of modern USB Flash drives, you can consider keeping your passphrase-protected private keys on removable media rather than in your home directory. This lets you use them from multiple locations while maintaining only one copy.
OpenSSH has a number of configuration options that can be employed to reduce risk. Most Linux distributions come with good middle-of-the-road settings. In some cases, these can be adjusted for better security. These include:
Logging: turn up the logging level to INFO or DEBUG. Examine your logs on a regular basis, or better yet, implement one of the many available log analysis tools to notify you of anomalies:
LogLevel INFO
Privilege separation: make sure the UsePrivilegeSeparation option is enabled:
UsePrivilegeSeparation yes
Protocol: limit OpenSSH to accept only protocol version 2:
Protocol 2
Root logins: prevent root logins using passwords. This limits root access only to nonpassword methods like public key authentication. If you never directly log in as root, set this to no. You always can log in as a regular user and su to root or use sudo. Setting this to without-password allows only nonpassword methods, such as public key authentication:
PermitRootLogin without-password
File permissions: make sure StrictModes is enabled to prevent the use of insecure home directory and key file permissions:
StrictModes yes
Reverse name checking: require OpenSSH to check proper reverse hostname lookups. Note that you must have proper name lookups working (that is, DNS or /etc/hosts):
VerifyReverseMapping yes
Prevent port forwarding: these two options prevent OpenSSH from setting up TCP port and X11 forwarding; if you do not need these features, disable them:
AllowTcpForwarding no X11Forwarding no
Disable all host-based authentication: always make sure these are disabled. These methods assume that the network can be trusted and allow .rhosts-style authentication based on hostname or IP. Never use these methods as primary authentication:
IgnoreRhosts yes HostbasedAuthentication no RhostsAuthentication no RhostsRSAAuthentication no
Using the above suggestions, you will be able to tighten access controls and eliminate sloppy trust relationships. As I mentioned earlier, staying current with updates or patches is critical to system security. In order to stay current, you need to be informed of when updates are released, so I suggest a multipronged approach. First, automate apt, yum or up2date to check nightly and report on missing updates. Second, subscribe to your Linux distribution's security mailing lists. Third, subscribe to one of the many security discussion groups, such as SecurityFocus. If you build OpenSSH from sources, join the OpenSSH mailing list to watch for updates.
System security requires a holistic approach. The methodologies provided in this article form the basis for securing OpenSSH, one small component of a modern complex Linux system.
After you believe you have secured your system, use scanning tools like the free Nessus Vulnerability Scanner (see Resources) and peer review from colleagues to check your work.
Resources for this article: /article/9023.
Matthew E. Hoskins is a Senior UNIX System Administrator for The New Jersey Institute of Technology where he maintains many of the corporate administrative systems. He enjoys trying to get wildly different systems and software working together, usually with a thin layer of Perl (locally known as “MattGlue”). When not hacking systems, he often can be found hacking in the kitchen. Matt is a member of the Society of Professional Journalists and can be reached at matt@njit.edu.