Port knocking is a mechanism that allows sensitive services provided by a server to be kept hidden and protected by a firewall until a specific sequence of requests are made to some other selected ports. This sequence of requests is called knocking. Which ports and the order that they must be knocked are a secret shared only to those authorized to access that service externally. After a successful knocking, the firewall opens the sensitive service port to the knocking host for a short time, so that a connection can be established.
Although not infallible (it was never intended to be), port knocking is a good mechanism to conceal sensitive services running on a server, like, for example, secure shell (SSH). Many people conceal their SSH service by changing the port it listens for new connections to something other than the default (TCP/22). This provides very little additional security and it is not enough when used alone.
There are daemon/tool-based solutions for port knocking, like knockd and The Doorman, but they have some inherent problems:
There is this iptables-only solution, which fixes the first problem. It uses only iptables and so is controlled directly from the firewall; you have no extra daemon running, which is a good thing. I decided to improve on that solution.
When you are behind a firewall that restricts the ports that you can communicate, it becomes difficult to rely on TCP or UDP port knocking. Restricting the set of ports used would reduce the number of possible knocking sequences, severely weakening the solution. Also, it is difficult to predict which ports you'd find open in every network you may happen to use.
So, I decided to look for something that should not be blocked: ICMP ping requests. ICMP is one of the core protocols of the Internet, and have fundamental importance for the operation of the network, from error signaling to link testing. No sane network administrator would block outgoing ICMP packets, since it has the potential to break a lot of things.
Ping requests can be easily created using the ping tool present on all *nix systems. Also, the ping tool present in all Linux, BSD and Mac OS X systems allows the user to supply a custom padding for the packet, up to 16 bytes. This pattern can be used to contain a secret, the "knocking sequence".
If 6 bits per byte are used for the knocking sequence (in order to avoid having binary data in iptables rules), it would result in 96 bits of entropy. Comparing it with the TCP port knocking solution, this solution provides approximately the same entropy of a 6-port TCP port knocking sequence.
So, after defining the 16-byte knocking sequence, these iptables rules should be applied:
iptables -N knock
iptables -N knock-ok
iptables -A knock -p icmp --icmp-type echo-request \
-m string --string "[16-byte-secret]" --algo bm --from 60 --to 61 \
-m recent --set --name knocked \
-j LOG --log-prefix "KNOCK-OK: " --log-level 5
iptables -A knock -m recent --name knocked --update --seconds 60 -j knock-ok
These commands create two new chains:
In order to knock the server, these commands should be used in the connecting host:
knockseq=$(echo -n "[16-byte-secret]" | hexdump -e '16/1 "%02x" "\n"') ping -c -p"$knockseq" hostname
The iptables rule is very strict with regards to the offset in the packet where the secret appears. It may be necessary to loose the restriction a little if you have a different protocol stack (IPv6 in particular). In this case, try --from 60 --to 76 or even --from 0 --to 96 for debugging. This has the side effect of cycled (rotated) secrets being accepted (for example, from "ABCDEFGHIJKLMNOP" to "EFGHIJKLMNOPABCD"), loosing some bits of entropy.