A critique of port knocking

1180

Author: 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.

Category:

  • Security