Linux.com

Everything Linux and Open Source

A critique of port knocking

August 10, 2004 (8:00:00 AM)  -  5 years, 3 months ago

By: Arvind Narayanan

Suppose you want to be able to retrieve files from your Linux system remotely. The "standard" method of running the SSH server on port 22 is notoriously inadequate. OpenSSH, which is the SSH server on the majority of Linux installations, suffers from regular exploits of buffer overflow and other vulnerabilities, and you neither have the time to keep up with the patches nor want to make the effort -- you'd rather put up with not being able to access your files. This is where port knocking might seem to help -- but don't count on it.
Port knocking is a method of "message transmission across closed ports." It works like this: initially a firewall blocks all ports on the server. The client issues a series of connection requests (knocks) to different ports; these are, of course, dropped since the ports are blocked. However, there is a daemon that monitors the log files for connection requests, and the sequence of requests serves as an encrypted code. If the code makes sense to the daemon, it enables SSH or another service (for a particular IP address and on a particular port encoded by the knock sequence).

Port knocking acts as an extra layer of security. It is also claimed to have the advantage of stealth: an attacker can't detect whether you're using port knocking or not.

But how effective is it?

Let's dispose of the stealth argument first. Suppose that port knocking becomes widely used (and surely that's the point of its existence?). Say it is running on 10% of all servers. Then an attacker simply assumes that the target machine is using port knocking and proceeds to attack it. The attack succeeds with a probability of one in 10 (of what it would have been if stealth were not employed), but those odds are worthwhile to the attacker, considering that the cost of the attack is far smaller than the expected gains from a successful attack. This kind of technique is quite common in port scanning -- crackers use multiple automated methods, each of which has only a small probability of success, but which taken together prove very effective.

The stealthiness of port knocking is limited in scope -- your machine appears uninteresting to someone who's scanning a whole range of IPs for open ports. A more dedicated attacker has a multitude of methods to infer that the target machine could be using port knocking, such as sniffing the (highly distinctive!) stream of packets in the knock sequence, observing that a machine known to host a service appears to have no ports open, social engineering, or even something as silly as finding a entry like "I got port knocking working today! w00t!" buried deep in the user's Weblog.

Security through obscurity is bad when it is the sole defense. This is true in a general theoretical sense, and there is always a cost associated with making the system more complicated in order to add an extra obscurity layer. In the case of port knocking, the hoops you need to jump through to achieve stealth far outweight the marginal benefits. Furthermore, there are much cleaner ways to achieve stealth, as we will see in a moment.

Once stealth is taken out of the equation, it becomes clear what port scanning really is. A port is not a physical object but merely a 16-bit integer. A sequence of knocks is therefore just a sequence of bits that lets you in -- in other words, a passphrase. So port knocking is simply a method to authenticate yourself with a passphrase. The obvious question then is, in what way is this convoluted and inefficient method of sending a passphrase across a network better than the straightforward method of putting it in a packet and sending it? The obvious answer is, it is not. (The second obvious question is, how is it different from authenticating yourself with a passphrase using SSH? But we'll get to that shortly.)

Think of security as an onion. If properly designed and implemented, each layer presents a new hurdle to the attacker. This is called "defense in depth." But the onion structure comes at a cost: it becomes much more difficult to analyze the security of the system. All too often, the layers serve as obfuscation and give you an illusion of security while leaving a single weak point open to an attacker.

To avoid falling into this trap, build your onion from standard components and to put them together in standard ways. On the other hand, everything about port knocking is about implementing crypto primitives in an obscure manner. If you're using a key, you call it a key. You gain nothing by calling it a sequence of port numbers; in fact, you lose a lot. When you think of a key as a key, it's clear that you shouldn't store it in any public location. On the other hand, log files (which contain the key in the form of port sequences) can lie around for ages. This is a typical example of how nonstandard terminology and implementation complicates analyzing the security of a system.

There are two reasons why a port knocking attacker may be thwarted: one is that only some ports are allowed in knock sequences (customized by the user) and the second is that the knock sequence can be encrypted with a password. The first defense is an extremely poor one. Security should reside only in knowledge of the key, and not in bits and pieces sprinkled throughout the system. That's Kerchoffs' law, often used as a rallying cry against security through obscurity.

The second defense is much better, but amazingly, it is presented as optional in the port knocking overview page. There is in fact little or no security in port knocking without encrypting knock sequences. Even with it, there are problems.

Firstly, the unnecessary only-some-ports-valid "protection layer" complicates things. Suppose you decide on a list of 32 valid ports (the current implementation allows up to 256). How long does the port knock sequence need to be? You might think that since each port is a 16-bit integer, you need 8 knocks, so that you get 8*16 bits or 128 bits of security (virtually unbreakable). But since each port has only 32 possible values (5 bits), what you actually get is only 8*5=40 bits of security (trivially breakable)! Granted, this is a problem for the implementer and not for the end user, and the current (proof-of-concept) implementation appears to take care of the length issue. But it is easy to go wrong. This is a particularly serious problem, since there is bound to be pressure on the implementer to make the knock sequence as short as possible, since it can easily take a minute to complete a port knock. When trying to reach a compromise between the two conflicting goals, security flaws result.

Secondly, there's the matter of what you're going to do about replay attacks. A replay attack is one where a router sitting between the client and the server passively sniffs what the client is sending, and sends the same bitstream after a while to the server, pretending to be the client. The standard defense is a challenge-response protocol, but that is of course ruled out because we don't want the server to send anything until the client authenticates itself. The documentation says that a flag field is added to the knock sequence and incremented for each port knock, preventing replay attacks. But this is nonstandard and dubious. What happens when the server is rebooted? What if the counter resets? As far as I can figure out, only a single-byte field is allocated for the flag, which makes resetting a real threat.

In general, port knocking has too many points of potential attack. In particular, anyone with non-root access to the server should be able to trivially break the system. This does not seem to be of concern to the developers.

Lessons from port knocking

There are several security lessons we can take from port knocking. There is a demand for barebones authentication system without bells and whistles that doesn't require maintenance. The problem with OpenSSH is that it tries to do everything. When designing a barebones system, it is essential to keep the target audience in mind and to decide the scope in terms of what features it will offer and what attacks it is designed to resist. I like the fact that the port knocking implementation is not written in C, a language which is subject to buffer overflow bugs. The approach of layering port knocking over SSH instead of replacing it is certainly sound. On the other hand, reinventing cryptography is definitely unsound.

Let us therefore take the port knocking out of port knocking and see what we get. It is a simple system for manipulating firewall rules based on standard password-based authentication and symmetric key encryption. By implementing this hypothetical daemon in a high level language and zealously resisting the urge to add any other features, we can greatly reduce the bugginess and possibly achieve a reasonable approximation to zero-maintenance. Stealth can be added: have the service run on a UDP rather than a TCP port. Since UDP is connectionless, the concept of closed and open ports is much weaker than for TCP. By having the application send out an ICMP_PORT_UNREACH message whenever a packet is received on that port (but nevertheless processing the packet), it can be made to look like a closed port to anyone who doesn't know the password. The extra obscurity might be useful if the overall system is simple enough. On the other hand we might forgo stealth and implement a challenge-response protocol instead to reliably stop replay attacks.

In conclusion, even though port knocking addresses a problem that many face, the solution is needlessly complicated and far from optimal.
Read in the original layout at: http://www.linux.com/archive/articles/37888