Just security.
Port knocking: a stealthy system for network authentication across closed ports
Port Knocking has not been seen on TV
port knocking > details > application

Details of Port Knocking Mechanism

Perl prototype: v0.30

  • pcaplib support added; daemon no longer requires firewall log file

2004-Nov-14 18:59 | ...more

new Net::Pcap support added to sniff packets directly ...more

Once you've perused the firewall primer, learn about the details of port knocking here. Ideas about how to use port knocking in simple situations are presented, as well as an outline of how to use encryption to avoid eavesdropping.

application

This section provides some examples of how port knocking can be used to increase the security of a hypothetical networked host. Try using the Knocking Lab to construct your own port knocks. The main strength of port knocking is the facility to close all ports to all incoming IP addresses, while maintaining a mechanism of establishing a connection using a proper knock sequence. Risks associated with application vulnerabilities are mitigated if ports remain closed and are opened only by legitimate users. Quite a bit of the examples below are taken from the article in SysAdmin magazine (June, 2003).

opening ssh (tcp/22) with a simple knock sequence

Suppose you have a networked system and you need to connect using ssh. Telnet has finally become taboo, and the use of ssh is wide spread. Since ssh can itself provide a tunnel for various ports, it is sufficient to use this single application for user sessions.

If you have a host which is not running any public network service (mail, web, etc) you can maximally protect this host by closing all privileged and all well-known ports. To close these ports using IPCHAINS, the following commands need to be issued.

ipchains -p tcp -s 0/0 -d FIREWALL/32 -p 0:1023 -j DENY -l
ipchains -p tcp -s 0/0 -d FIREWALL/32 -p 1024:49151 -j DENY

This will cause ports 0-1023 to refuse any TCP connections, without sending ICMP error packets back to the client. The IP of the networked host is FIREWALL. Each connection attempt will be logged. If someone from IP address CLIENT attempts to connect to FIREWALL the log file will show entries such as

Feb 12 00:13:26 ... input DENY ... CLIENT:64137 FIREWALL:102 ...
Feb 12 00:13:27 ... input DENY ... CLIENT:64138 FIREWALL:100 ...
Feb 12 00:13:27 ... input DENY ... CLIENT:64139 FIREWALL:100 ...
Feb 12 00:13:28 ... input DENY ... CLIENT:64140 FIREWALL:103 ...

A daemon monitoring the log file can detect these connection attempts to ports 102, 100, 100, 103 from the same IP address. This particular port sequence could trigger the daemon toopen port ssh (tcp/22). The daemon would execute the following command

ipchains -I input -p tcp -s CLIENT/32 -d FIREWALL/32 22 -j ACCEPT

Another sequence can be used to close the port. For example, 103, 100, 100, 102 could be used to trigger the deletion of the rule that was dynamically created to allow CLIENT to connect.

ipchains -D input -p tcp -s CLIENT/32 -d FIREWALL/32 22 -j ACCEPT

In this example, a remote user has opened port ssh (tcp/22) to IP address CLIENT by making TCP connections to ports 102, 100, 100, 103 and subsequently closed the ssh (tcp/22) port to their IP by knocking on ports 103, 100, 100, 102.

Encrypted Port Knocks

The 4-port knocks in the previous example provided limited protection against packet sniffing, since the knock was independent of the connecting IP address. Anyone on the network looking at packets could reconstruct the sequence and use it to gain access to the ssh (tcp/22) port. In order to reduce the risk of the knock being deconstructed and gainfully executed by a third-party, it should contain the client IP address and be encrypted.

For example, the knock could be composed of the following

IPb1, IPb2, IPb3, IPb4, PORT, TIME, CHECKSUM

where IPbx is the x byte of the IP address to which port PORT will be opened for TIME minutes. Using the IP address in the knock allows you to open the port to an arbitrary IP address, not just the one initiating the knock. For example, to open port ssh (tcp/22) for 15 minutes for the IP address 142.103.205.1 the knock would be composed of

142, 103, 205, 1, 22, 15, 233

The final CHECKSUM field provides validation of a proper knock sequence and can be defined as

CHECKSUM = (IPb1+IPb2+...+TIME) mod 255

In the above example, the check sum is 488 mod 255 = 233. The 7 digits (in the range 0-255; thus, these are bytes or chars) making up the knock can be encrypted using some appropriate method (e.g. DES, Blowfish, RSA), or a one time pad for maximum security. I'll talk about one time pads later. The client would carry out the following steps

encrypt(KNOCK) = KNOCK_ENCRYPTED
 encode(KNOCK_ENCRYPTED) = KNOCK_ENCRYPTED_ENCODED

where the encode(KNOCK) step would be done to map the encrypted knock onto a range of ports (e.g. 255 ports in the range 745-1000). If the knock is composed solely of values in the range 0-255 and the encoding is into unsigned chars (0-255) then the length of the encoded sequence is always the same. The port knock daemon monitoring the file would attempt to apply the process

KNOCK_ENCRYPTED = decode(KNOCK_ENCRYPTED_ENCODED)
          KNOCK = decrypt(KNOCK_ENCRYPTED)
             OK = verify_checksum(KNOCK)

to every incoming sequence. If the sequence was malformed, the DECRYPT step would fail. If the sequence was successfully decrypted, the CHECKSUM would have to match in order for the sequence to be passed.

Encrypting with Perl

I'll show an example of using Perl to encrypt and decrypt a port knock sequence. The module Crypt::CBC is a very useful block-chaining cryptography module which acts as a proxy to the implementations of encryption algorithms like Crypt::IDEA, Crypt::Blowfish, etc. The implementation typically require that data be processed in same-sized chunks and the block-chaining module allows encryption of arbitrary strings. To try this code out you must have the Crypt::CBC and Crypt::XXX modules installed if you want to use the XXX encryption algorithm. These are available from CPAN.

The first thing to do is create a cipher object using Crypt::CBC. To create the cipher, a password is needed (e.g., "knock") and the name of the algorithm to encode the knock (e.g., Blowfish). In the act of encryption an IV (initialization vector) can be used to ensure that encrypting the same data will result in a different encrypted string. This is an important part of encryption. In this example, an IV vector is not used (prepend_iv=>0) to keep the knock sequence short.

use Crypt::CBC;
# keyphrase for the encryption
use constant KEY => "knock";
# encryption algorithm
use constant CIPHER => "Blowfish";
my $cipher = Crypt::CBC->new({key        => KEY,
                              cipher     => CIPHER,
                              iv         => "01234567",
                              prepend_iv => 0});

Now that a cipher is prepared, the a list of values can be encrypted. Let's use the list (142,103,205,1,22,15) as from the example above.

# create the knock payload array
my @data = (142,103,205,1,22,5);
# compute checksum and push it onto the data array
push(@data,sum(@data) % 255);
# @data = (142, 103, 205, 1, 22, 15, 233)
# pack the data into a string (assume array elements are unsigned chars) 
my $ciphertext = $cipher->encrypt(pack("C*",@data));
# unpack the ciphered data into unsigned chars
my @cipherpack = unpack("C*",$ciphertext);
# create the knock sequence by mapping to ports PORTMIN ... PORTMIN+255
my @knocks = map {PORTMIN+$_} @cipherpack;
# @knocks = (966, 914, 795, 964, 831, 862, 807, 932)

Decrypting the knock sequence requires the creation of a cipher and the knowledge of the proper password and algorithm. Given the encrypted knock sequence (966, 914, ..., 932), first the sequence is mapped back to the range 0-255 and then it is decrypted.

my @knocks_unmapped  = map {$_-PORTMIN} @knocks;
# @knocks_unmapped = (221, 169, 50, 219, 86, 117, 62, 187)
my $ciphertext       = pack("C*",@knocks_unmapped);
my @knocks_decrypted = unpack("C*",$cipher->decrypt($ciphertext));
# @knocks_decrypted = (142, 103, 205, 1, 22, 15, 233)

Encrypting 7 numbers produces an 8 digit knock sequence. Remember that initialization vector I talked about above? Let's implement an IV and see how it changes the knock sequence. In this case, the IV will be a random 4 digit number.

my $iv_vector = join("",map{1+int(rand(9))} (1..4));
my $cipher = Crypt::CBC->new({key        => KEY,
                              cipher     => CIPHER,
                              iv         => $iv_vector,
                              prepend_iv => 0});

Given the same knock payload as before, (142, 103, 205, 1, 22, 15, 233), here are some encrypted knock sequences for various initialization vectors.

initialization vector: 9137
knock sequence [20 knocks]: 827 842 855 845 856 854 818 831 802 794 
                            796 800 767 851 762 822 761 930 820 947

initialization vector: 7836
knock sequence [20 knocks]: 827 842 855 845 856 854 818 831 800 801 
                            796 799 866 995 802 848 930 925 755 905

initialization vector: 1248
knock sequence [20 knocks]: 827 842 855 845 856 854 818 831 794 795 
                            797 801 807 997 829 914 879 976 963 872

As promised, the encrypted sequence is different if different IV values are used. The use of an IV increases the size of the knock sequence to 16+n for an IV of length n (e.g., an 8 digit IV will create a 24 digit knock sequence).

initialization vector: 64999848
knock sequence [24 knocks]: 827 842 855 845 856 854 818 831 799 797 
                            802 802 802 801 797 801 846 813 993 931 
                            798 972 951 928
last updated 2010-Oct-17 10:16
Port Knocking (c) 2002-2017 Martin Krzywinski