Setting up port knocking

Posted under: Security
- Tagged: port knocking, SSH

What we want to do is to be able to knock on three ports in the correct sequence, when we do the firewall will open for an SSH connection from our IP for a period (let's start with 10 seconds) and then after the period, automatically close behind us.

First let's get knockd installed, on Debian it's as simple as

sudo apt-get install knockd

Now we'll get a warning saying that knockd is disabled and not starting, and that we need to edit /etc/default/knockd. But before we do that, let's set up the sequence knockd looks for and the command it runs. This is held in /etc/knockd.conf

[options] 
        UseSyslog

[openSSH]
        sequence    = 7000,8000,9000
        seq_timeout = 5
        command     = /sbin/iptables -A INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
        tcpflags    = syn

[closeSSH]
        sequence    = 9000,8000,7000
        seq_timeout = 5
        command     = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
        tcpflags    = syn

This is the default config that knockd comes with. First let's set up the sequence we'll knock to have knockd open the port. You can really set it to almost any three ports you want as long as they're not one of the commonly used ports that may be used by another process (a list is available on Wikipedia here). But since I was thinking of making a quick Python program to knock the ports for me let's go ahead and pick some random ones, I'll use random.org to get three random numbers between 1024 and 65535 (the last port number). For the sake of example, let's use the port numbers 59577, 63663, 10717, none of which appear to be common ports. Go back to /etc/knockd.conf and comment out the section for [closeSSH] and change it to match the below.

[openSSH]
        sequence      = 59577,63663,10717
        seq_timeout   = 5        
        tcpflags      = syn
        start_command = /sbin/iptables -A INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
        cmd_timeout   = 10
        stop_command  = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT

Here we're setting the sequence, the period (seq_timeout) in which we have to knock the ports as well as the command that will be run and then the stop command which will be run after the timeout (cmd_timeout). The above example assume you have SSHD listening on port 22, if not change the --dport section to the port number SSHD is listening on.

Now before knockd will start, we'll have to enable it (as we were told in the error when it was installed). Edit /etc/default/knockd for editing and look for the START_KNOCKD=0 line and change it to START_KNOCKD=1. Slightly below that, you may need to specify the network interface. In this case of this server, eth0 was not correct and knockd would not start automatically. To find the interface you can use ip addr list and find the interface with your public facing IP listed, in my case it's venet0. So we'll go back into /etc/default/knockd, uncomment the line KNOCKD_OPTS="-i eth0" and remove eth0 and replace it with venet0 (or whichever interface you found above).

Now knockd should start.

For port knocking, on Linux you can use the program knock which comes with knockd or in our case, let's use a quick Python program for it.

#!/usr/bin/python3

import argparse
import socket

def knock(host, port):

    conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    conn.setblocking(0)
    try:
        conn.connect((host, port))
    except socket.error:
        print('Knocked on {} {}'.format(host, port))

def knock_range(host, ports):

    for port in ports:
        knock(host, port)

if __name__ == '__main__':

    arg_parser = argparse.ArgumentParser(description='Simple Port Knocker.')
    arg_parser.add_argument('Host', help='The host you wish to knock on')
    arg_parser.add_argument('Ports', nargs='*', type=int, help='List of ports to knock on')
    args = arg_parser.parse_args()
    host = args.Host
    ports = args.Ports
    if ports:
        knock_range(host, ports)
    else:
        print('No ports listed, try listing some ports to knock on')

Usage of the above would be:

./portKnock.py defestri.org  59577 63663 10717