2022-01-07
security
Port knocking is a cool way of hiding services on your server until a secret "knock pattern" is sent. Let me present a practical example of how this can be used in real life.
In my blog post about SSH I mentioned that moving the SSH daemon to a non-default port (i.e. something else than 22) can help you avoid being hammered with login requests. I still believe this is useful - however now I'd like to show how something similar can (perhaps better) can be achieved by using port knocking.
Wikipedia says the following about port knocking:
In computer networking, port knocking is a method of externally opening ports on a firewall by generating a connection attempt on a set of prespecified closed ports. Once a correct sequence of connection attempts is received, the firewall rules are dynamically modified to allow the host which sent the connection attempts to connect over specific port(s). [...] The primary purpose of port knocking is to prevent an attacker from scanning a system for potentially exploitable services by doing a port scan, because unless the attacker sends the correct knock sequence, the protected ports will appear closed.
So far so good, but how does this work in practice? The following real-life example is on a Debian Linux server using sysvinit
(not systemd
- so it needs some adjustments if that is how you roll).
First of all you need knockd
- there are probably other packages too to implement port knocking but this is simple enough.
The following is my /etc/knockd.conf
(well, not exactly mine, but highly similar one):
[options]
UseSyslog
[opencloseSSH]
sequence = 21001:udp,21002:tcp,21003:udp
seq_timeout = 5
tcpflags = syn
start_command = /etc/init.d/ssh start
cmd_timeout = 5
stop_command = /etc/init.d/ssh stop
I use the following little script to "knock" properly, then use SSH to log in:
#!/bin/bash
echo | nc -4 -u my-server.net 21001
echo | nc -4 -G 1 my-server.net 21002
echo | nc -4 -u my-server.net 21003
for i in {1..20}; do
sleep 0.1
t0=`date +%s`
ssh -4 my-server.net
if [ $? == 0 ]; then
exit
fi
t1=`date +%s`
if [ $(($t1-$t0)) -gt 5 ]; then
exit 0
fi
done
As you can see it sends the appropriate sequence using nc (netcat
), then connects in a loop for a couple of times with some wait in between.
Ok, so what does this look in real life? Here's a snippet of the logs when I log in. It shows knockd
recognising the pattern, and executing the defined command (open sshd
) as a result. Five seconds later it stops sshd
:
Jan 2 16:48:50 my-server knockd: A.B.C.D: opencloseSSH: Stage 1
Jan 2 16:48:50 my-server knockd: A.B.C.D: opencloseSSH: Stage 2
Jan 2 16:48:50 my-server knockd: A.B.C.D: opencloseSSH: Stage 3
Jan 2 16:48:50 my-server knockd: A.B.C.D: opencloseSSH: OPEN SESAME
Jan 2 16:48:50 my-server knockd: opencloseSSH: running command: /etc/init.d/ssh start
Jan 2 16:48:55 my-server knockd: A.B.C.D: opencloseSSH: command timeout
Jan 2 16:48:55 my-server knockd: opencloseSSH: running command: /etc/init.d/ssh stop
As a result, another log shows sshd
starting up and accepting the connection:
Jan 2 16:48:50 my-server sshd[2004]: Server listening on 0.0.0.0 port 22222.
Jan 2 16:48:50 my-server sshd[2004]: Server listening on :: port 22222.
Jan 2 16:48:50 my-server sshd[2005]: Accepted publickey for me from A.B.C.D port 50170 ssh2: ECDSA SHA256:X...
Jan 2 16:48:55 my-server sshd[2004]: Received signal 15; terminating.
Some further notes:
When the machine boots, sshd
by default starts and it is seemingly left open. However, knockd
stops it within 5 seconds. If you don't trust this behaviour then you can opt not to start sshd
at all on boot. Depending on whether you use systemd
or sysvinit
(or whatever else) you need to do this differently.
knockd
stops sshd
after five seconds regardless of whether you are logged in or not. It's worth noting that stopping sshd
does not terminate existing connections, so this is a safe operation - i.e. your SSH session will not be terminated.
The above configuration uses IPv4. One can set up an IPv6-only version or one that listens to knocking on IPv4 or IPv6.
An example configuration in the knockd
man page uses iptables
to open/close the firewall whereas sshd
itself is kept running. This is also a common way of doing it - just don't forget that you may need to open/close the IPv6 firewall too! Otherwise you may think your service is not available whereas it still is on IPv6.
I recommend you exercise a plan B for your logins in advance, before you start experimenting with locking yourself out (as you may succeed!). For example make sure you can still log in using the console, and only then start fiddling with hiding your remote access.