If you require remote access to your computer and you enable Secure Shell (SSH) connections, you must accept that you will automatically attract hackers who will try to break your defenses and take command of your machine. Although there’s no guarantee that your machine won’t be “0wn3d” by a “h4x0r,” a few simple solutions can help reinforce your SSH door and make life a bit more difficult for anybody trying to break in. This article considers three such techniques:
- 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.
- Completely hiding the fact that you even allow SSH access and requiring a special “knock” sequence to be recognized as a possible user.
To apply these techniques, you need to access the root account. Also, you’ll probably have to install some packages, and you’ll need to configure your firewall and your router—if you have one—to open and close specific ports and forward them to your machine.
Reinforcing the door
The concept “security through obscurity” is well known—and well derided—because doing things in an obscure way, hoping that no one will get wise to your method, is just asking for problems. However, in some contexts, a bit of obscurity can help. Although simple measures cannot stop determined hackers, at least you can be better defended against “script kiddies”, whose scripts usually aren’t that thorough.
Everybody knows that the standard port for SSH connections is 22. So, the first step you should take to make your machine more secure is simply to change the port to another unused and nonstandard port number—say, 22960. Numbers above 1024 are usually safe, but check the references to avoid possible problems. This change simply means that you have to use this command line to connect to your computer:
ssh ‑p 22960 your.machine.url
To effect this bit of subterfuge, make a simple change in the /etc/ssh/sshd_config file. Edit it (you must work as root for this), look for the line that reads
Port 22, and change the number to whatever you have chosen (If the line is commented out because it starts with a pound sign [
#], remember to uncomment it). Save the file, and restart SSH with the command
/etc/init.d/sshd restart. You should also open the chosen port in your firewall and close port 22.
But you can do even more. Fiddle with the configuration file so that it includes the lines shown in Listing 1. Note that some of these lines may already exist, but they could be commented out.
Listing 1. Some changes to your SSH configuration file enhance security at little cost
Port 22960 LoginGraceTime 30 MaxAuthTries 3 Protocol 2 PermitRootLogin no
LoginGraceTime allows 30 seconds for a login; if the user takes longer than this, he or she won’t be allowed access and will have to relog.
MaxAuthTries limits the user to three wrong attempts before the login attempt is denied. The
Protocol 2 line avoids using a somewhat weaker protocol. Finally, the last line doesn’t allow anybody to log in as root, making life a bit more difficult for hackers. You could also use the
AllowGroups options for extra restrictions. These changes don’t substantially add to the security of your machine, but a common script that just tries brute force attacks at the standard port 22 will fail harmlessly. In any case, consider this a first step in the right direction. Further along in the article, you’ll use even safer methods to not only change the port number but to completely hide it.
Who can enter?
For most people, PAM is a cooking oil that comes from a can. But in terms of Linux® security, PAM stands for Pluggable Authentication Modules. These modules provide extra authentication rules to harden access to your machine.
Let’s start with a basic question: Why use PAM at all? If every program had to define its own authentication logic, it would be a mess. How could anybody be certain that all applications implemented the same tests and checks? What would you do if extra controls were needed—reprogram everything? In computer science, it’s sometimes said that everything can be solved with an extra layer, and at least for security, that stands true. If a program needs to authenticate a user, it can call the PAM API. The API will take care of running all the checks that you might have specified in the PAM configuration files. This method even allows you to modify the authentication rules on the fly, because all PAM-aware programs start applying the new rules without the need for any changes in their code. If you want to use some biometric checks (such as iris scanners or fingerprint readers) and the manufacturer provides you with a PAM, you’re set. When you include the module call in the configuration files, your device will be available to all your applications.
PAM provides four distinct security areas, though it isn’t likely that your applications will require all of them. For example, the
passwd command only requires the third group in the list below:
accountdeals with account limitations. Given that the user is valid, what is he or she allowed to do?
authhas to do with user identification—for example, through entering a user name and password.
passwordhas to do exclusively with password-related functions, such as establishing a new password.
sessiondeals with connection management, including logging.
You will have to create a configuration file in the /etc/pam.d directory for each application that will use PAM, and the name of the file will be the same as the application’s name. For example, the configuration file for the
login command would be /etc/pam.d/login.
You have to define which modules will be applied, creating a “stack” of actions. PAM runs through all the modules in the appropriate stack and, depending on their results, grants or denies the user’s request. You must also define whether or not checks are mandatory. Finally, an other file provides default rules for any application without specific rules.
optionalmodules may succeed or fail; PAM returns
failuredepending on whether any module eventually succeeds.
requiredmodules are mandatory. On failure, PAM returns
failure, but only after running the rest of the modules in the same stack.
requisitemodules also need to succeed. But if they fail, PAM immediately returns
failurewithout running any other modules.
sufficientmodules, when they succeed, cause PAM to immediately return
successwithout running any other modules.
The structure of the configuration files is simple. You can include comments, which start with a hash character (
#); continue long lines by ending them with a backslash (
\). Lines have three fields: the area (
session), the control flag (
sufficient), the path to the module that will be run, and any possible parameters. Note that the second field can be more complicated; see Resources on the right for more information. Also, you can use
include rules, as in
auth include common-account, to include rules from other files.
The special /etc/pam.d/other file is a “default” configuration file (see Listing 2) whose rules are automatically applied to applications without specific configuration files of their own. To be safe, give the /etc/pam.d file a quick review and just rename all configuration files that you don’t use (so that the
other configuration will be used). If you decide you actually need the application, just rename the configuration file to its original name. The default configuration usually denies all requirements (by using the
pam_deny.so module) and warns the administrator (via the
pam_warn.so module) so that he or she can fix the situation.
The standard “other” configuration file provides a secure default (which denies everything) for all applications without a configuration file of their own.
Listing 2. The standard “other” configuration file
account required pam_deny.so auth required pam_deny.so auth required pam_warn.so password required pam_deny.so password required pam_warn.so session required pam_deny.so
If you replace
pam_unix.so, the standard authentication method (enter a user name and password) will be applied. Likewise, if you do not care about security, use
pam_permit.so instead, which happily allows any request!
Some available modules
Although there is no standard list of modules, all distributions include most of the following options. Check the /lib/security or /usr/lib/security directories, where modules reside. For 64-bit operating systems, substitute
lib. If you need more information, you can try
man the.name.of.the.module, but do not attempt to execute it directly; PAMs are not executable binaries.
pam_accessallows or denies access according to file /etc/security/access.conf. You’ll use this module later to decide which users are allowed to log in.
pam_pwcheckcheck possible new passwords for strength.
pam_permitare basic and always deny or allow access.
pam_echoshows the content of a given file to the user.
pam_lastlogshows the user the date and time of his or her last login.
pam_ldap.solets users authenticate against an LDAP server to provide centralized authentication across a network.
pam_limitsmodule lets you specify system resource limits, as defined in the file /etc/security/limits.conf.
pam_listfileprovides yet another way to allow or deny services based on the contents of a file.
pam_mailchecks whether the user has pending mail.
pam_motddisplays the “message of the day” file to the user.
pam_nologinblocks all logins should a file named /etc/nologin exist.
pam_rootokallows access to the root user without further checks. This module is usually included in /etc/pam.d/su; the required line is
auth sufficient pam_rootok.so. Root can act as any other user without having to provide a password.
pam_succeed_ifchecks for specific desired account characteristics, such as being a member of a particular group.
pam_timecan restrict access to a service, as specified by the rules in /etc/security/time.conf.
pam_unix2) provides classical UNIX® style authentication per the /etc/passwd and /etc/shadow files.
pam_userdbprovides authentication against a Berkeley database.
pam_warnlogs information in the system logs.
pam_wheelonly provides root access to members of group wheel; the required line is
auth required pam_wheel.so.
Check the resources on the right section for information on more modules and even on writing your own modules. Now, turn to using PAM so that you can decide who can log in to your machine.
Limiting access with PAM
Now, let’s use PAM to restrict who is going to be allowed to connect to your server. You must edit the /etc/pam.d/sshd file so that it reads like Listing 3.
Listing 3. Adding pam_access.so to the sshd PAM file
#%PAM‑1.0 account include common‑account account required pam_access.so auth include common‑auth auth required pam_nologin.so password include common‑password session include common‑session
pam_access.so to the sshd PAM file lets you easily define who can use SSH to connect to your machine. The
pam_access.so module implements security controls based on the /etc/security/access.conf file, as shown in Listing 4.
Listing 4. With pam_access.so, you can define who can or cannot use SSH
+ : ALL : 192.168.1. + : jack : ALL + : jill : ALL ‑ : ALL : ALL
The first line enables everybody (
ALL) to log in from within the internal network. The next two lines allow users
jill to access the server from any location. The last line is a “catch-all” and denies everybody else access from every other address. Another way of enabling several users access is to use
pam_listfile.so, creating a list of approved users (for example, /etc/ssh_users). Add the following line to the /etc/pam.d/sshd file:
auth required pam_listfile.so item=user sense=allow file=/etc/ssh_users onerr=fail
You’re not done yet. You have to modify the /etc/ssh/sshd_config file more so that it uses PAM. Add a
UsePAM yes line to the file, restart the
sshd daemon, and that’s all!
Is there a door at all?
Even if you have applied the methods in both previous sections, you must assume that hackers will try to get through any open doors in your system, no matter your precautions. Changing the SSH port number is just a small bother to a knowledgeable would-be intruder, and the limitations placed on users also help (provided no user falls prey to a hacking or social engineering attack that reveals his or her password). However, the mere fact that there’s a door to your system is enough to tempt hackers.
The last scheme for adding security to your machine is the boldest one. You will close the open port making your machine virtually impregnable to any attack. Open it only to a user who can provide a “secret knock”, that opens the required port so that he or she can enter his password and gain access to the machine.
This technique, called port knocking, is appropriate for users who require access to servers that aren’t available to the public. The server can keep all of its ports closed until the user has provided a secret knock sequence (a sequence is easy to implement and requires modest resources).
When the secret ports are open, the usual security mechanisms (such as passwords or certificates) will apply. All services that require the secret ports will function correctly with an extra security layer provided at the firewall level.
The point of this method is to close all ports and monitor external attempts to make connection. Whenever a predefined and specific sequence of attempts is recognized (called a knock sequence), you can execute actions like opening a port so the outsider can get in. The knock sequence can be as complex as you wish—from a simple list (such as trying TCP port 7000; UDP port 7100; TCP port 7200) to a collection of use-once-only sequences. (In cryptography terms, this concept is similar to “one-time pads”, the most secure encryption method known.) The outsider must know the port number and the password to use SSH, as well as the knock sequence required to open that port and enable the password. Without the sequence, connection attempts just mutely fail.
Why is this a good safety scheme? There are 65,535 possible ports (see resources on the right side for more information). Even if you consider the ports already assigned, you’re still left with over 60,000 available ports. If you settle on a sequence just four “knocks” long, a hacker trying to guess it by brute force would have to test about 13 million million million possible sequences (that’s 13 followed by 18 zeros). It should be obvious that such an attack isn’t likely to work! Of course, never assume that brute force or blind luck are the only possible ways to guess the correct sequence. That’s the reason you’re not using a single security method; instead, you have a series of security layers making life more complex for hackers.
You have to install the
knockd knocking daemon; it monitors the knock sequences and acts on detecting a valid one. If you wish, you can build it from source, but as this package is available in most (if not all) distributions. You’ll do better using your package-management tool. For example, in OpenSUSE, you could install it with
Yast2 or by executing
sudo zypper install knockd. In Ubuntu, you could similarly use
sudo apt-get install knockd and in Debian use
sudo aptitude install knockd. Just search for knockd with your distribution’s software-installation tool, and you’ll be set.
After installing the package, you must edit the /etc/knockd.conf file to specify your port-knocking rules, and then you’ll have to start your daemon running. You must know how your firewall works to do the required setup. For example, in OpenSUSE, you could use something like the setup shown in Listing 5.
Listing 5. A sample configuration file designed for the OpenSUSE firewall
[opencloseSSH] sequence= 7000,8000,9000 tcpflags= syn seq_timeout= 15 cmd_timeout= 30 start_command= /usr/sbin/iptables ‑s %IP% ‑I input_ext 1 ‑p tcp ‑‑dport 22960 ‑j ACCEPT stop_command= /usr/sbin/iptables ‑s %IP% ‑D input_ext ‑p tcp ‑‑dport 22960 ‑j ACCEPT
The above sample enables SSH access after successive knocks on ports 7000, 8000, and 9000, respectively.
knockd, close port 22960 and try to log in remotely. The attempt should fail, as shown below in Listing 6.
Listing 6. If you close SSH access and don’t start the knock daemon, login attempts merely fail.
> ssh the.url.for.your.site ‑p 22960 ‑o ConnectTimeout=15 ssh: connect to host the.url.for.your.site port 22960: Connection timed out
Now try that again after starting the port-knocking daemon by using
sudo /etc/init.d/knockd start or
sudo knockd -d; the commands are equivalent. The port-opening sequence requires knocking at ports 7000, 8000, and 9000. You have 15 seconds to accomplish this sequence. The port opens after recognition, and you have 30 seconds to log in. Otherwise, the port will close again.
To verify this process, go back to your remote machine and log in. This time provide the required knocks, as shown in Listing 7. Note that the
knock command is usually installed when you get
knockd. Otherwise, just use your distribution’s package-management tool and look for it.
Listing 7. A successful login after having provided the required sequence of knocks
> knock the.url.for.your.site 7000 > knock the.url.for.your.site 8000 > knock the.url.for.your.site 9000 > ssh the.url.for.your.site ‑p 22960 ‑o ConnectTimeout=10 Password:
If you provided a wrong sequence of knocks (or no knocks at all), you would receive a “Connection timed out” message, and the SSH port would remain completely closed with no sign of its existence.
The /etc/knockd.conf file has a general options section,
options, and a section for each knock sequence you want to use. You can write options in uppercase, lowercase, or mixed case.
- As standard,
knockdmonitors the eth0 interface. To work with a different interface—for example, eth1—you can include a
Interface=eth1line. Note that you just include the device name not the full path to it.
- If you want to enable logging, you can opt for the standard Linux log files by including a
useSyslogline, or you can use a specific file of your own by including
LogFile=/the/full/path/to/your/file. However, be aware that logging is a weakness; should a hacker get his or her hands on a log, that intruder would have the port-knocking sequences in the clear.
- If you want to be able to check whether
knockdis still running, include
PidFile=/the/full/path/to/a/PID/file. The process ID (PID) of the daemon will be stored in the file. You should have a
crontask that periodically checks whether
knockdis still alive and restarts it, if needed. Note that in the case of a crash, your system will be safe; all ports will be closed and unaccessible. However, until the daemon starts running again, you won’t be able to log in.
You can have
knockd listen for several sequences and react in different ways to each one. In the earlier example, you had
knockd open the SSH port; you could just as easily have enabled the HTTP port so a web server is accessible or run a specific process. You will have a section in the configuration file for each sequence.
sequenceto define the sequence of knocks, as in
7000,8000,9000. By default, knocks use TCP, but you can add UDP to mix it up a bit more, as in
- As an alternative to having single fixed sequences, you can specify a file with “one-time sequences” that, after they are used, are erased and cannot be used again. To specify such sequences, use:
Use any text editor to create the file; it should have a sequence (in the format shown above) in each line. You should have a copy of this file on your remote machine to remember how to log in.
- You can specify which incoming TCP packets to scan and discard those that don’t match the flags
URG. Over an SSH connection, you should use
- You can define a maximum time for completing a sequence with
Seq_Timeout=seconds.to.wait. If the complete sequence isn’t entered in this time, it won’t be recognized and access won’t be granted.
- Similarly, you can set a maximum time for the user to execute a second command after the sequence was recognized with
Cmd_Timeout=seconds.to.wait. If the user who provided the knock sequence doesn’t act quickly (logging in, for example), the port will close again.
- A most important parameter is
Start_command=some.command.to.execute, which specifies what command or script is to be executed after a successful knock sequence is recognized. If you need to refer to the knocker’s IP address (for example, to allow a connection from his or her machine to yours), you can use
%IP%. It is then replaced by the correct value at run time. In the example above, you wrote:
/usr/sbin/iptables ‑s %IP% ‑I input_ext 1 ‑p tcp ‑‑dport 22960 ‑j ACCEPT
iptablesopens port 22960 for the user at the IP address from where the knock sequence came.
- The other important parameter is
Stop_command=some.command.to.execute; it is executed after the
Cmd_timeouttime has elapsed.
In this case, because you just wanted to open or close port 22960, a single command sufficed. If your needs are more complicated, you can invoke a script and do whatever you want—even if it doesn’t involve opening ports at all. You could trigger any action, such as running a process or performing a backup. Of course, learning what command to use can be tricky. For example, because I was running OpenSUSE and it provides its own firewall front end, I had to examine the output of
iptables -l to learn what command I should provide to open or close port 22960.
knockd itself, there are few options to consider:
-c: Lets you specify a configuration file other than the default, /etc/knockd.conf
knockdrun as a background daemon, which is the standard way to run it
-D: Provides output debugging messages
-h: Provides help on syntax and options
-i: Allows you to change the interface to watch from the default
-l: Enables DNS lookup for log entries—a bad practice, given that it forces your machine to use DNS traffic, which means dropping stealthiness
-v: Provides more verbose messages and explanations
-V: Displays the version number of the program
Finally, to produce the knock sequence itself, you could use many methods, but programming
knock is the simplest way to go about it.
knock the.url.for.your.site 7000
for knocking TCP port 7000 and either:
knock the.url.for.your.site ‑u 8000
knock the.url.for.your.site 8000:udp
for knocking UDP port 8000. The
-h parameter provides some help for this command.
You’ve seen three ways of hardening SSH access to your machine: modification of sshd configuration parameters, selecting which users can log in by means of PAM, and use of port-knocking sequences to hide the existence of SSH access. Although I mentioned that there is no way to secure fully any machine, adding these three layers will make your server more than just a little safer.