Introduction - What is Port Knocking
Port knocking is a simple but clever way to hide services that are listening for network connections from
would-be attackers. The idea is that you have to "knock" at a predetermined random port in order to
then connect to the service at a different port, such as SSH or VNC. Some more information on
port knocking can be found on Wikipedia:
Wikipedia Port Knocking Artical
Please note that my method is not the most flexible since it uses pure IPTABLE rules for the setup, but
because of this you will get to know iptables, as well as the theory behind why and how port knocking works.
Plus there are no packages to install and once you understand how IPTABLES works, it becomes so
simple even a caveman can do it.
Why would you even bother?
Recently, I noticed my log files that log SSH activity filling up with entries such as:
"Failed password for illegal user xxxxxxx". This was basically a string of brute force SSH attacks.
Brute force attacks try a list of commonly used passwords and usernames to see if the attacker can
gain access to the system by guessing the login information. To do this they typically use a program
that can try thousands of usernames and passwords per minute, which can be quite annoying if it's
your system being attacked.
Although my passwords are randomized and so the attacks were not a real threat, I decided to try
and secure my server a little to stop these brute force SSH attacks (mainly coming from china).
What I needed was a simple way to stop the attacks altogether, but still allow ssh access from anywhere
for myself since I log in to check things to tweak my server a lot. In order to do this though,
it became obvious that I needed a way to make it look like my computer did not accept SSH connections
at all, but still accept connections from myself. This required a simple way to "authenticate" myself
to the server if you will. SSH already authenticates itself though, so a software solution that
I would log into and authenticate to and then open up SSH for me was not really what I was looking for.
What I decided on was the Port Knocking solution. With this setup, I could simply "knock" on a port
that only I knew was doing anything, and thereby open up the SSH port to listen to my IP address only.
This also has a bonus feature of being able to "knock" on a different port to then make SSH stop
allowing my IP address to connect anymore.
Please note that this does have serious limitations though.
Most notably is that if you are connecting through a NAT (Network Address Translation) anyone from
behind the NAT will then be able to try and log in as well, but unless China is behind your NAT I
think the normal SSH authentication will still be fine protection here :)
Introducing IPTABLES
IPTABLES is the firewall in the linux operating system. It will be installed by default on 99% of all
Linux Installations. There are several modules within IPTABLEs which can be used for various things.
For this setup, we will use the module named "recent" which is designed to detect malicious
access attempts and then block or track the IP address of the potential attacker.
Here are the commands used to build my IPTABLES rules to enable port knocking. These are run from the
command prompt, but you can just as easily modify them to run as a script on boot or add them inside an
iptables configuration file.
iptables -N PORTKNOCK
iptables -A INPUT -j PORTKNOCK
iptables -A FORWARD -j PORTKNOCK
iptables -A PORTKNOCK -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A PORTKNOCK -m state --state NEW -m tcp -p tcp --dport 22 -m recent --rcheck --name SSH -j ACCEPT
iptables -A PORTKNOCK -m state --state NEW -m tcp -p tcp --dport 22 -j DROP
iptables -A PORTKNOCK -m state --state NEW -m tcp -p tcp --dport 1600 -m recent --name SSH --set -j DROP
iptables -A PORTKNOCK -m state --state NEW -m tcp -p tcp --dport 1601 -m recent --name SSH --remove -j DROP
And now I will go through a brief explaination of each rule above.
For further information, please view the full IPTABLES manual
page located at the web address below (I noticed many others online are far from complete):
IPtables Manual
iptables -N PORTKNOCK
This command creates a new Chain named "PORTKNOCK". A Chain is like a set of rules within the firewall.
You can tell the firewall to sent certain packets to one chain, and other packets to
another chain, with each chain having different rules so that you don't have to duplicate rules accross
multiple chains, etc. By default, there are 3 chains already
built-in. Those are "INPUT", "OUTPUT", and "FORWARD".
iptables -A INPUT -j PORTKNOCK
This tells all packets that are in the "INPUT" Chain to set them to apply to the "PORTKNOCK" chain instead.
This will let us create a set of rules in our PORTKNOCK chain so that it is seperated from the rest
of the IPCHAINS structure. The "INPUT" Chain is where all packets start at that have their
source IP address as your server.
iptables -A FORWARD -j PORTKNOCK
This tells all packets that are in the "FORWARD" Chain to set them to apply to the "PORTKNOCK" chain instead.
This will let us create a set of rules in our PORTKNOCK chain so that it is seperated from the rest
of the IPCHAINS structure, and will also let us create a set of rules once instead of having to
create them for both the "INPUT" and the "FORWARD" chains.
The "FORWARD" Chain is where all packets start at that have their source IP address as a computer
other than your server (if your computer does routing for instance). You can probably skip this
command if you have routing going on, otherwise anyone connecting through the router on port 22 would
have to "knock" on the router to get through!
iptables -A PORTKNOCK -m state --state ESTABLISHED,RELATED -j ACCEPT
This allows all existing connections and new connections that spawn from existing connections to
flow through the firewall freely. Without this, your SSH connection would be terminated if the SSH
port was closed on you. With this command, even if the SSH port is told not to allow new
connections from your IP address, you're existing connections will still work just fine.
iptables -A PORTKNOCK -m state --state NEW -m tcp -p tcp --dport 22 -m recent --rcheck --name SSH -j ACCEPT
This is the most important command. It says that for any NEW connection
(not already existing like the previous command), and if the destinaton port is TCP 22 (the SSH port)
then check to see if the source IP address is in the list "SSH" and if they are then allow the
connection. IP addresses can be added to the "SSH" IP address list
or removed from the list with IPTABLES rules as well, which is why this whole process works
(see commands below).
iptables -A PORTKNOCK -m state --state NEW -m tcp -p tcp --dport 22 -j DROP
This simply says to drop all traffic that is part of a NEW connection (not existing connections) and is
on TCP port 22. This is applied after the above command, so basically if the source IP address is not
in the "SSH" list as defined in the previous command, then drop the packets (don't allow the connection).
iptables -A PORTKNOCK -m state --state NEW -m tcp -p tcp --dport 1600 -m recent --name SSH --set -j DROP
This says that if a NEW packet comes in on TCP port 1600, add the source IP address to the "SSH" list so that they can
then have access to the SSH port. Remember that a previous command checked the "SSH" list for the
IP address and allowed the packets in that case. This adds the IP address to that list.
iptables -A PORTKNOCK -m state --state NEW -m tcp -p tcp --dport 1601 -m recent --name SSH --remove -j DROP
You'll notice this is almost the same as the previous command, except "--set" became "--remove" and the
port changed from 1600 to 1601. As you probably guessed, this will remove the source IP address
from the "SSH" list if you open a TCP connection to port 1601.
Checking it out
Okay, so it's set up right? First lets test it out! Since you havn't knocked on port 1600 yet,
try opening a SSH connection to port 22. You should get a timeout error and have the connection
fail. Also, you can view the "SSH" list of allowed IP addresses by viewing the "SSH" list located
in /proc/net/ipt_recent/*
shell> cat /proc/net/ipt_recent/SSH
Now lets knock on port 1600 by sending a TCP packet to that port in the form of a telnet connection.
This should unlock the SSH port for us by adding us to the "SSH" IP address list.
shell> telnet your.server.ip.address 1600
Next, check out the SSH list in /proc/net/ipt_recent/ and you should see your IP address there with
other information about the connection (note, I "knocked" from a computer with IP address 192.168.0.1).
Then try to open the SSH connection, it should succeed!
Also note that although the SSH port is open for you, it's still closed to all other IP addresses that
have not "knocked" on port 1600 yet. You can even "knock" on port 1601 to hide the SSH port from
your IP address again:
shell> telnet your.server.ip.address 1601
Check out /proc/net/ipt_recent/SSH and notice that your IP address is gone now. Also notice the
SSH connection you opened still works, but you cannot open a new connection anymore.
Conclusions, and Taking it Further
You can also have one port knock open another port you have to knock at, which openes another port
you have to knock at, which finally opens the SSH port. You can also have it work with any other
port, not just SSH of course.
As a final thought though, do not rely just on this alone for security. In fact, a simply nmap scan
of your system would show the following:
shell> nmap localhost
Starting Nmap 4.03 ( http://www.insecure.org/nmap/ ) at 2006-12-31 03:28 EST
Interesting ports on localhost.localdomain (127.0.0.1):
(The 1668 ports scanned but not shown below are in state: closed)
PORT STATE SERVICE
1600/tcp filtered issd
Nmap finished: 1 IP address (1 host up) scanned in 1.812 seconds
Notice the 1600 port listed there? Not only can someone see this port open, but a scan could easily
open the SSH port for that IP address! For this reason, I would recommend NOT using the above ports,
and choosing ports that are not typically scanned by default scanners. Also, I would recommend having
several "close" knocking ports, so that the scanner will probably hit one of those and re-close the port
before scanning the SSH port even if it does hit the port that opens the SSH port. For scanners that
scan the ports in sequence you will probably want to have the ports just below and above the "open"
knock port to be "close" knock ports.
In reality though, this protection is best against people scanning only for the specific service
you are trying to protect. If they are singling your server out, you've got bigger problems than
brute force attacks and other security measures should be in place.
Another option is to use something like portsentry. Portsentry can listen on certain ports or can be
configured to listen to all ports. More importantly, portsentry can be
configured to do something when someone hits a port it is listening for. By default this is to
block the source address with a route rule, but I have set mine to block using an IPTABLES rule.
For my configuration, I have
portsentry listening on ALL ports (not recommended for a real server though since this adds a
decent load when you have multiple connections to the server). Any connections to
any port except 80, 443, and 22 are logged and the source IP address is blocked via IPTABLES and the
deny.hosts file. What that means is that if an attacker tries to connect to ANY port except 80, 443,
or 22, portsentry will add rules to block ALL traffic from that host from then on.
What makes that setup so nice is that people cannot figure out the "knock" port by trial and error. One
slip-up and they are banned! Maybe you don't want to be as harsh though. Well, portsentry claims to
be able to only block after a certain number of attempted connections, but I have not been successful
in configuring that so I can't confirm, but it should be possible.