Set up port knocking

Port knocking is a method that aims at modifying the behavior of a firewall in real time by causing the opening of ports for communication by first launching series of connections on distinct ports in the right order, like knocking on a door.

In this article, we will see how to unlock the SSH port by knocking three ports.

The port knocking technique provides an additional safety feature, but does not replace other security practices.

Information and requirements

These elements are to be taken into consideration to follow this article:

Update the system

sudo dnf -y update

Install development tools

sudo dnf -y groupinstall 'Development Tools'

Install required packages

The libpcap-devel package provides a portable framework for low-level network monitoring. This library can provide network statistics collection, security monitoring and network debugging. Since almost every system vendor provides a different interface for packet capture, the libpcap authors created this system-independent API. It allows to alleviate the need for several system-dependent packet capture modules in each application.

sudo dnf --enablerepo=powertools -y install libpcap-devel

Clone the knockd repository

This repository is a popular knockd daemon and knock client that we will use to set up our port knocking system.

git clone https://github.com/jvinet/knock.git /tmp/knock

Build the source code

Autoconf tools are used to create buildable source code for Unix-like systems.

cd /tmp/knock
autoreconf -f -i

Flag explanations:

Create the Makefile

By defining the prefix to /usr/local/, we indicate where the executable file should be located.

sudo ./configure --prefix=/usr/local/

Compile the binary

sudo make

Install the compiled binary

sudo make install

Delete the directory

sudo rm -rf /tmp/knock

And now, let the real stuff begin.

Create the config file

Retrieve your interface name and replace <interface> in the configuration file by what is outputed.

ls /sys/class/net | tr " " "\n" | sed -n '1p'

Copy the following snippet into /etc/knockd.conf.

[options]
        logfile = /var/log/knockd.log
        interface = <interface>

[opencloseSSH]
        sequence        = 
        seq_timeout     = 5
        cmd_timeout     = 5
        start_command   = /usr/bin/firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="%IP%" port protocol="tcp" port="22" accept' && /usr/bin/firewall-cmd --reload
        tcpflags        = syn
        stop_command    = /usr/bin/firewall-cmd --permanent --remove-rich-rule='rule family="ipv4" source address="%IP%" port protocol="tcp" port="22" accept' && /usr/bin/firewall-cmd --reload

Once copied, execute the following snippet to generate 3 pseudorandom ports with the shuf command.

sudo sed -i "s/        sequence        =/        sequence        = $(shuf --random-source=\/dev\/urandom -i 1-65535 -n 1),$(shuf --random-source=\/dev\/urandom -i 1-65535 -n 1),$(shuf --random-source=\/dev\/urandom -i 1-65535 -n 1)/" /etc/knockd.conf > /dev/null

In this file, we have two sections, options and opencloseSSH.

The first section contains:

The second section contains:

We are going to pay attention now to the sequence directive. A good practice is to generate pseudorandom numbers between 1 and 65535. The shuf command allows you to generate pseudorandom numbers (pseudorandom permutations). By using the shuf command three times in a row, three pseudorandom numbers are going to be generated. These numbers will be the ports to be knocked to start the execution of the start_command directive. Theses series of numbers must absolutely remain secret.

To make it simple, with this configuration, the client will have 5 seconds to knock the port series defined via the sequence directive. If the knocked ports are the right ones, the command defined via the start_command directive will be executed, (allow the source IP address retrieved via %IP%) that knocked the ports to connect to SSH. Finally, 5 seconds after this command is executed, the command defined via the stop_command directive will be executed, (disallow the source IP address retrieved via %IP%) that knocked the ports to connect to SSH.

Create the service file

Copy the following snippet into /etc/systemd/system/knockd.service

[Unit]
Description=Knockd daemon
Documentation=https://www.zeroflux.org/projects/knock
After=network.target

[Service]
LimitMEMLOCK=infinity
LimitNOFILE=65535
LimitNPROC=64
RestartSec=2s
Type=simple
User=root
Group=root
ExecStart=/usr/local/sbin/knockd
ExecStop=/usr/bin/pkill knockd
Restart=always
ProtectSystem=full
CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN

[Install]
WantedBy=multi-user.target

Restart systemctl daemon

sudo systemctl daemon-reload

Enable and start knockd daemon

sudo systemctl enable --now knockd.service

Check the service

sudo systemctl status knockd.service

Retrieve the ports to knock

Knockd is now configured. The following command fetches the ports to be knocked and encrypts them.

grep "sequence" /etc/knockd.conf | sed 's/[^,0-9]*//g' | tr ',' ' ' | openssl enc -aes-256-cbc -pbkdf2 -out /tmp/ports.enc.txt

Retrieve this file on your local workstation.

scp cloud.local:/tmp/ports.enc.txt .
sudo mv ports.enc.txt /etc/
sudo chmod 400 /etc/ports.enc.txt

Delete it from the server.

rm -f /tmp/ports.enc.txt

Remove the SSH service from firewalld

By default, the SSH service is enabled. Because we want to protect this one, we have to disable it.

sudo firewall-cmd --remove-service=ssh --permanent
sudo firewall-cmd --reload

Install the client

In order to knock out the server, you have to install a client. In the same way that there is the knockd daemon on the server, there is the knock client. Follow steps 6 (Clone the knockd repository) to 10 (Install the compiled binary) to install the knock client on your computer.

When it is done, you should have the knock command available on your system.

Try to knock

knock cloud.local $(openssl aes-256-cbc -d -pbkdf2 -in /path/to/ports.enc.txt); sleep 1; ssh cloud.local

This command knocks the server cloud.local on the ports contained into the encrypted file /path/to/ports.txt (which has been decrypted by openssl before), waits 1 second and connects to the server via SSH.

I let you write an alias to make it more user-friendly.

Check logs on the server

sudo tail -f /var/log/knockd.log

The timestamps were removed to save space, but you should have something like this.

10.0.0.1: opencloseSSH: Stage 1
10.0.0.1: opencloseSSH: Stage 2
10.0.0.1: opencloseSSH: Stage 3
10.0.0.1: opencloseSSH: Stage 4
10.0.0.1: opencloseSSH: Stage 5
10.0.0.1: opencloseSSH: OPEN SESAME
opencloseSSH: running command: /usr/bin/firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.1" port protocol="tcp" port="22" accept' && /usr/bin/firewall-cmd --reload
10.0.0.1: opencloseSSH: command timeout
opencloseSSH: running command: /usr/bin/firewall-cmd --permanent --remove-rich-rule='rule family="ipv4" source address="10.0.0.1" port protocol="tcp" port="22" accept' && /usr/bin/firewall-cmd --reload

Stage {1,2,3,4,5} informs us that the configured ports have been knocked out. The OPEN SESAME text informs us that the port will open (that the start_command directive will be executed), finally 5 seconds later, the stop_command directive is executed.

As you can see, in the rich rule, the variable %IP% has been replaced by the source IP address.

Debug things

Execute knockd as debug mode

With debug mode, logs are more verbose and written on the standard output so you can clearly see any errors. Exit this mode with Ctrl+c.

sudo /usr/local/sbin/knockd -D

Kill knockd process if executed as daemon mode

sudo pkill knockd

You have now installed a port knocking system on your server. A good practice would be to monitor the /var/log/knockd.log file and configure your monitoring tool to send you a notification as soon as OPEN SESAME is written in the log file.