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:
- The manipulations are carried out on Rocky Linux 8.
- A connection via an SSH key pair is already configured.
- Throughout this post, we will access the server via
cloud.local
which is related to its local IP address.
Update the system
[operator@cloud ~]$ sudo dnf -y update
Install required tools
[operator@cloud ~]$ sudo dnf -y install vim git make autoconf automake gcc
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.
[operator@cloud ~]$ 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.
[operator@cloud ~]$ git clone --depth 1 https://github.com/jvinet/knock.git
Build the source code
Autoconf
tools are used to create buildable source code for Unix-like systems.
[operator@cloud ~]$ cd knock/
[operator@cloud knock]$ autoreconf -fi
Flag explanations:
-f
flag remakes even configure scripts and configuration headers that are newer than their input files (configure.ac
and, if present,aclocal.m4
.-i
flag installs the missing auxiliary files in the package.
Create the Makefile
By defining the prefix to /usr/local/
, we indicate where the executable file should be located.
[operator@cloud knock]$ sudo ./configure --prefix=/usr/local/
Compile the binary
[operator@cloud knock]$ sudo make
Install the compiled binary
[operator@cloud knock]$ sudo make install
And now, let the real stuff begin.
Create the config file
Retrieve active interface name and replace <interface>
in the configuration file by what is outputed.
[operator@cloud ~]$ nmcli -t -f name conn show --active
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.
[operator@cloud ~]$ 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:
logfile
: write log actions to a file.interface
: network interface to listen on. Only its name has to be given. In my case I have only one interface (in fact two, withlocalhost
) which name isens32
. The first command surrounded with parenthesis is used to find the interface name. Modify it according to your hardware and configurations.
The second section contains:
sequence
: specify the sequence of ports.seq_timeout
: time to wait for a sequence to complete in seconds. If the time elapses before the knock is complete, it is discarded.cmd_timeout
: time to wait betweenstart_command
andstop_command
. This directive is optional, only required ifstop_command
is used.start_command
: specify the command to be executed when a client makes the correct port-knock.tcpflags
: only pay attention to packets that have this flag set.stop_command
: specify the command to be executed whencmd_timeout
seconds have passed sincestart_command
has been executed.
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
[operator@cloud ~]$ sudo systemctl daemon-reload
Enable and start knockd daemon
[operator@cloud ~]$ sudo systemctl enable --now knockd
Check the service
[operator@cloud ~]$ sudo systemctl status knockd
Retrieve the ports to knock
Knockd is now configured. The following command fetches the ports to be knocked and ciphers them.
[operator@cloud ~]$ grep "sequence" /etc/knockd.conf | sed "s/[^,0-9]*//g" | tr "," " " | openssl enc -aes-256-cbc -pbkdf2 -out /tmp/ports
enter aes-256-cbc encryption password:
Verifying - enter aes-256-cbc encryption password:
Retrieve this file on your local workstation.
scp cloud.local:/tmp/ports .
sudo mv ports /etc/
sudo chmod 400 /etc/ports
Delete it from the server.
[operator@cloud ~]$ rm -f /tmp/ports
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.
[operator@cloud ~]$ sudo firewall-cmd --remove-service=ssh --permanent
[operator@cloud ~]$ 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 /etc/ports); sleep 1; ssh cloud.local
This command knocks the server cloud.local
on the ports contained into the ciphered 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
[operator@cloud ~]$ sudo tail -f /var/log/knockd.log
The timestamps were removed to save space, but you should have something like this.
[2022-08-16 23:12] 192.168.2.1: opencloseSSH: Stage 1
[2022-08-16 23:12] 192.168.2.1: opencloseSSH: Stage 2
[2022-08-16 23:12] 192.168.2.1: opencloseSSH: Stage 3
[2022-08-16 23:12] 192.168.2.1: opencloseSSH: OPEN SESAME
[2022-08-16 23:12] opencloseSSH: running command: /usr/bin/firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.2.1" port protocol="tcp" port="22" accept' && /usr/bin/firewall-cmd --reload
[2022-08-16 23:12] 192.168.2.1: opencloseSSH: command timeout
[2022-08-16 23:12] opencloseSSH: running command: /usr/bin/firewall-cmd --permanent --remove-rich-rule='rule family="ipv4" source address="192.168.2.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
.
[operator@cloud ~]$ sudo /usr/local/sbin/knockd -D
Kill knockd process if executed as daemon mode
[operator@cloud ~]$ 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.