Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Mobile

Practical Secure Port Knocking


November, 2004: Practical Secure Port Knocking

John is vice president of engineering at Electric Cloud, which focuses on reducing software build times. He can be contacted at [email protected].


Last year, Martin Krzywinski described a technique for stealthily communicating with a computer (see "Port Knocking: Network Authentication Across Closed Ports," Sys Admin magazine, June 2003). The idea was that open ports on a machine invite attack. If you leave a machine on the Internet with an SSH daemon running on port 22, it's a simple matter for attackers to use port scanners like nmap (http://www.insecure .org/nmap/) to discover that the port is open and then try to attack it.

Krzywinski suggested that sensitive ports should be left closed until opened using a secret knock. The knock consists of sending TCP SYN packets (the first packet sent when opening a TCP connection) to a sequence of closed ports on the target machine. Firewall software records in a log file the failed connection attempts and software watching the firewall log checks for a specific sequence of ports and enables/disables a service. For example, with the correct knock, an SSH daemon could start waiting for connections, or the firewall could be reconfigured to allow connections from the host that knocked.

Since a knock is just a sequence of attempted TCP connections, programs such as telnet can be used to manually generate it. For example, to knock on ports 42, 196, and 69 of 192.168.0.3, do:

telnet 192.168.0.3 42
telnet 192.168.0.3 196
telnet 192.168.0.3 69

Imagine that Linux host 192.168.0.3 is running an SSH daemon on port 22, but the iptables firewall (http://www.netfilter .org/) has been set to drop and log every incoming TCP packet, thus making access to the SSH daemon impossible:

iptables -N LOGNDROP
iptables -A INPUT -p tcp -j LOGNDROP
iptables -A LOGNDROP -j LOG --log-level warn \
--log-prefix='FIREWALL:'
iptables -A LOGNDROP -j DROP

This tells iptables to create a new chain called LOGNDROP, then pass all incoming TCP packets to the chain where they are first logged (most likely to /var/log/messages) with the prefix FIREWALL: (for easy grepping), then dropped.

The use of the action DROP means that TCP packets are not acknowledged in any way. The packet is simply discarded. The host machine will not even generate an ICMP "port unreachable" packet and the host appears to be switched off, but the connection attempt has been logged. To outsiders, the system appears to be inoperative, but the log file tells a different story.

An application watching /var/log/messages sees entries such as those in Example 1 if a knock is made against ports 42, 196, and 69 from host 192.168.0.5. The same application could process the ports knocked to decide to add or remove entries in iptables, thereby opening or closing ports. For example, the correct knock on 42, 196, 69 could result in the firewall opening port 22 just for host 192.168.0.5 for SSH access with the iptables command:

iptables -I INPUT -p tcp -s 192.168.0.5 --
dport 22 -j ACCEPT

Port knocking has three fundamental ideas behind it:

  • Default to Closed. It's better to leave sensitive services firewalled until they are needed so that scanning the machine with nmap doesn't reveal any ports to attack.
  • Share a Secret. How ports get opened should rely on a secret (for example, a password in the form of a specific sequence of ports).
  • Vow of Silence. The port knocking application does not respond to any packets. It listens but divulges no information about its operation, or even existence, so that it too is undetectable with nmap.

A slightly more controversial tenet of port knocking is that the knock should be hard to intercept. Some people accuse port knocking of relying on "security through obscurity": that is, both the sequence of ports to knock is secret and it's not possible to determine the sequence.

Obviously the simplistic port knocking system just outlined has vulnerabilities and it is possible to detect the knocks using a packet sniffer. Eavesdroppers can easily detect the knock sequence using, for example, tcpdump (http://www.tcpdump.org/) with an appropriate filter:

tcpdump -t -n '(tcp[13] == 2) or (tcp[13] == 18)'

which prints out all TCP SYN and SYN/ACK packets. For example, intercepted knocks on 42, 196, and 69 would look like Example 2. A Perl script (Listing One) can quickly parse tcpdump's output to determine which SYN packets received a SYN/ACK (hence, were real connections) and which were silently dropped (and hence could be a knock), then produce the output:

Knock on 192.168.0.3:42
Knock on 192.168.0.3:196
Knock on 192.168.0.3:69

Once the knock is known, it can be repeated by a third party to open the SSH port for their use at any later time.

Because of this vulnerability, encryption of the knock is essential (for more information on encryption techniques, see http://www.portknocking.org/). The knock needs to be nonreplayable (that is, eavesdroppers can't reuse the knock for their own use), nonspoofable (eavesdroppers can't reuse the knock from a different IP address), and should not be easily decodable.

Tumbler

The Tumbler protocol (named for the parts of a lock that tumble into place when the right key is inserted) implements the spirit of port knocking with robust security using a well-known hashing algorithm. Tumbler provides protection against replay attacks (the knock is timestamped and cannot be reused after a short interval) and spoofing (the knock can only be used from a specific IP address).

In addition to the protocol, a Perl implementation is available under the General Public License at http://tumbler.sf.net/. Of course, there's no reason whyTumbler has to be implemented in Perl—the protocol is simple and could easily be built into other applications (for example, SSH could include a --tumbler option to perform the appropriate knock before connecting).

The protocol consists of a single UDP packet in the form:

TUMBLER<v>: <knock>

where <v> is the protocol version number, currently 1, and <knock> is a hexadecimal string containing the knock. For example, the UDP packet might contain:

TUMBLER1:844c17eee03d848cc0a60e90f6
08d5ea11f417d9bf0d2c1af2b5

There is no response to the message. Either the process listening for Tumbler messages accepts it or it is silently dropped.

The <knock> is created by hashing the following three pieces of information using the SHA256 algorithm (http://csrc.nist.gov/CryptoToolkit/tkhash.html):

  • The current Zulu date/time in the format YYYMMDDHHmm (YYY is the number of years since 1900, MM is the month starting with January as 0, DD is the day of the month starting with 1, HH is the Zulu hour in a 24-hour clock, mm is the Zulu minutes).
  • The dotted decimal representation of the sender's IP address (192.168.0.5, for example).
  • A shared secret password string.

SHA256 is used because it is a known secure hash algorithm (in fact, it's an NIST Standard) that produces a one-way hash of its plain text. Because Tumbler only needs to recognize the validity of a message, and doesn't need to decode it, a secure hash algorithm is appropriate. When a Tumbler message is received, the host creates a hash based on:

  • The current Zulu date/time on the machine.
  • The IP address of the person sending the Tumbler messages.
  • The shared secret password string.

It then compares the two hashes to see if the message is valid. If the hash matches, then the host can proceed to open the firewall. If any part of the message is different (for instance, the time is wrong, the IP address doesn't match, or the secret password is incorrect), then a different hash is generated and the host discards the Tumbler message.

If the host has multiple possible knocks, then each is configured with a different shared secret and the host runs through all the possible hashes looking for a match.

Implementation

In the Perl implementation (available electronically; see "Resource Center," page 5), a script named "tumbler" sends the knock using Example 3 (with $secret containing the user's secret password). It grabs the Zulu time by calling gmtime, gets the machine's IP address in dotted format by calling inet_ntoa (on an IO::Socket called $socket), then hashes the message using sha256_hex (which is part of the Perl module Digest::SHA; http://search.cpan.org/~mshelor/DigestSHA5.02/SHA.pm) and returns a hex string containing the SHA256 hash. Finally, it sends a single UDP packet containing the Tumbler message.

The receiving machine runs another Perl script named "tumblerd" (also available electronically), which is configured through the configuration file tumblerd.conf to listen on a UDP port for Tumbler messages and perform commands. In Listing Two, tumblerd has been configured to allow SSH connections once a knock with the password open-pAsSwOrD has been made, and there's even a knock that closes the port again (password close-pAsSwOrD).

For example, to open the SSH port, the remote user runs:

tumbler --open tumbler:
//open-pAsSwOrD@host:8675/

or alternatively:

tumbler --open tumbler://host:8675/

and then types in the secret password open-pAsSwOrD. The tumblerd runs through all the configured secrets looking for a hash match and, when it finds one, it runs the appropriate command.

A simple shell script containing three commands could establish an SSH connection to the host after opening the port with a knock and close the port again when the SSH connection is complete:

#!/bin/bash
tumbler --open tumbler:
//open-pAsSwOrD@host:8675/
ssh host
tumbler --open tumbler:
//close-pAsSwOrD@host:8675/

Security Properties

Tumbler clearly shares with port knocking the idea that ports are closed by default, and the use of a shared secret to open ports. To maintain the "vow of silence," tumblerd does not respond to packets sent, but it is possible to discover the existence of tumblerd using nmap UDP scanning if the firewall is not configured to drop unknown UDP packets.

To run a UDP scan a host with nmap, type:

nmap -sU host -p1-65535

This detects the tumblerd daemon running:

Starting nmap 3.30
( http://www.insecure.org/nmap/ )
Interesting ports on host (192.168.0.3):
Port State Service
8765/udp open unknown

nmap UDP scanning works by sending a packet to each port scanned and looking for the ICMP "port unreachable" message sent when the port is closed. If there's no port unreachable reply, then the port is open. To prevent that from happening, configure iptables to drop all UDP packets except those destined for tumblerd:

iptables -A INPUT -p udp
--dport 8675 -j ACCEPT
iptables -A INPUT -p udp -j DROP

Dropping the packets means that they are silently discarded. Tumbler guards against replay attacks, where eavesdroppers intercept Tumbler packets and try to reuse them in two ways:

  • Embedded in the hash is the Zulu time the knock was sent with an accuracy of one minute. The tumblerd daemon only accepts valid hashes; hence, the packet times out automatically.
  • tumblerd automatically discards duplicate hashes. Not only is the packet useless in under a minute, repeated use of it within the same minute has no effect.

To guard against spoofing, where eavesdroppers use an intercepted knock from a different IP address, Tumbler includes the IP address of the sender in the hash. For attackers to spoof the Tumbler protocol, they must intercept a packet and reuse it in under 60 seconds from the same IP address as the packet was originally sent.

Since the tumblerd passes the IP address of the sender to commands that it executes, the attacker would only be able to repeat exactly the same command as the original knock.

Tumbler is easier to sniff than a TCP SYN-based port knocking implementation because, once a single Tumbler packet has been intercepted, the destination port is known and can be tracked using tcpdump:

tcpdump 'dst port 8675'

Other Implementations

Tumbler is just one implementation of the port knocking idea. In addition to reading http://www.portknocking.org/, check out the following interesting projects: doorman (http://doorman.sourceforge.net/) and knockd (http://www.zeroflux.org/knock/). Most interesting of all is cd00r (http://www.phenoelit.de/stuff/cd00rdescr .html), which was intended for use in malware. An academic look at port knocking comes in the form of a research paper from Intel Research (http://www.intelresearch.net/Publications/Berkeley/012720031106_111.pdf).

DDJ



Listing One

#!/usr/bin/perl -w
use strict;
my knocks;
while ( <> ) {
   my $packet = $_;
   $packet =~ /^((\d+\.){3}\d+)\.(\d+) > ((\d+\.){3}\d+)\.(\d+)/;
   my ($src_ip,$src_port,$dest_ip,$dest_port ) = ($1,$3,$4,$6);
   if ( $packet =~ / ack / ) {
      knocks = grep( !/^$src_ip:$src_port$/, knocks );
   } else {
      push knocks, "$dest_ip:$dest_port";
   }
}
foreach my $k (knocks) {
   print "Knock on $k\n";
}
Back to article


Listing Two
# The common section contains configuration options for the tumblerd daemon,
#  here we set the UDP port to listen on to 8675 and a log file
[common]
   port = 8675
   log = /var/log/tumblerd.log
# Each door that a user can knock on is defined by a unique [door-X] section, 
# the first section is for opening the SSH port, and second for closing
#
# Each door has a secret (i.e. the password for this
# door that is part of the knock) and a command to execute.
#
# In the command it's possible to use the macros %IP% for the IP address of 
# the person who knocked and %NAME% for the name of the door (in the 
# first door here the name is open-ssh)
[door-open-ssh]
   secret = open-pAsSwOrD
   command = iptables -I INPUT -p tcp -s %IP% --dport 22 -j ACCEPT
[door-close-ssh]
   secret = close-pAsSwOrD
   command = iptables -D INPUT -p tcp -s %IP% --dport 22 -j ACCEPT
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.