Many of us, especially system/network admins, often leave the ssh port 22 open for remote access to the system. This leaves a door for the hackers to try to sneak into the system. A non-legit user can simply brute-force the system to connect to the open ssh port. One way of solving the issue in the above scenario is to hide the ssh port (or any other) using port knocking mechanism. In this article, I will show you how to implement port knocking using iptables.
iptables is a utility program that allows system administrators to configure the tables provided by the Linux kernel firewall.
Note: In case you want the rules for ipv6 you can use ip6tables to create rules for the firewall. Interested readers can also look at nftables which is an upgraded version of iptables and expected to replace it in future. Make sure that you have iptables installed in your system.
Two systems that are used:
Server 172.31.75.10 (Ubuntu)
Client: 172.31.72.153 (Kali Linux)
Port knocking is a mechanism to open a closed port of a system in a firewall by attempting to connect to some predefined closed ports. Consider the case of an ssh port. Initially, port number 22 will be closed and no one will be able to establish a connection with it. There will be firewall rules specifying that only after some ports, say 8000, 8001 and 8002, are knocked, ssh port will be opened. The specified port numbers are also closed.
For this tutorial, we will follow the given sequence: 5678, 5984 and 6357. It is preferable that the choice of ports is as obscure as possible. We will go more over this later in the post. Initially, the table is empty and can be cross-checked using the following command.
$ sudo iptables -L
Before beginning let’s see the state of the open ports using nmap. As you can see in the picture below that scanning the server shows four ports open namely, 22, 80, 443 and 8000. By the end of the tutorial port number 22 won’t be shown open by the nmap. A python SimpleHTTPServer is running on port number 8000.
Logical Design of the Port Knocking
First, we want our firewall to accept all the traffic that is not subject to the port knocking. This will include all the web server traffic (for port number 80, 443 and 8000).
After this, we need to forward the remaining traffic to a new chain that will deal with the port knocking. Let’s call this chain WALL. Post this we need to flag the IP addresses that try to connect to the first legal port. We will need a rule that will check if the second hit is on the second legal port. If it is, then we set another flag which will indicate that two ports have been knocked correctly. In case the second knock is wrong we drop the packet and the flag is reset. Similarly, other chains will work on the same strategy of checking the appropriate flag and passing it on if it continues on the right path. In the end, if an IP address correctly hits the ports in sequence ssh port is exposed will be exposed to it. It is preferable that the ssh daemon is exposed only for a brief period of time allowing the IP address to connect.
Logically we have three gates each requiring one knock to allow an IP address to get connected to the running service. This means that there are four different states for a requesting IP address to take:
- initial state: All the IP addresses will initially be in this state unless they have hit the first port correctly. In our case, the port is 5678.
- auth1 state: IP addresses those have correctly hit the first port will be flagged with the to auth1.
- auth2 state: If an IP address is flagged with auth2 then it implies that the IP address has correctly knocked the second port. Next packet from this state will determine whether to set the flag to initial or auth3 for the IP address.
- auth3 state: All the IP addresses that are flagged auth3 has successfully knocked the three ports, in the correct order. Any IP address with this flag is allowed to connect to the ssh port.
Four different chains that will be required are as follows:
- GATE1: Determines whether an address in the initial state should be flagged as “auth1”.
- GATE2: Determines whether an address in the “auth1” state should be processed to “auth2” or reset to the “initial” state.
- GATE3: Determines whether an address in the “aut2” state should be flagged as “auth3” to allow an SSH connection, or reset to the “initial” state.
- PASSED: This chain opens the port for SSH connection for legit clients.
Getting your Hands Dirty
It is always good to practice to set the default policies in an empty table to “ACCEPT” in order to maintain the current connections. Then we should flush the existing firewall rules and start the firewall fresh.
$ sudo iptables -P INPUT ACCEPT
$ sudo iptables -P FORWARD ACCEPT
$ sudo iptables -P OUTPUT ACCEPT
$ sudo iptables -F
Type the following commands to create additional chains that we will use:
$ sudo iptables -N WALL
$ sudo iptables -N KNOCK1
$ sudo iptables -N KNOCK2
$ sudo iptables -N KNOCK3
$ sudo iptables -N PASSED
After typing these commands we have an open firewall that we will restrict. There should be eight different chains in the firewall. You can crosscheck them by typing the following command:
$ sudo iptables -L
This will list all the chains and the rules.
As we have already talked we need to handle the traffic that doesn’t belong to the port knocking part. We need to add rules for that in the INPUT chain. Try the following commands:
$ sudo iptables -A INPUT -i lo -j ACCEPT
$ sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
$ sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
$ sudo iptables -A INPUT -p tcp --dport 8000 -j ACCEPT
The first command will allow the machine to accept all connections from the local machine, as different services and applications often need to communicate among themselves. The second and third command allows the traffic for ports (http and https) running the web server. The fourth command is because I have left the python SimpleHTTPServer running.
All these commands allow basic connections and now we can transfer the remaining traffic to the WALL chain.
$ sudo iptables -A INPUT -j WALL
Configuring the three KNOCK chains
$ sudo iptables -A KNOCK1 -p tcp --dport 5678 -m recent --name AUTH1 --set -j DROP
$ sudo iptables -A KNOCK1 -j DROP$ sudo iptables -A KNOCK2 -m recent --name AUTH1 --remove
$ sudo iptables -A KNOCK2 -p tcp --dport 5984 -m recent --name AUTH2 --set -j DROP
$ sudo iptables -A KNOCK2 -j KNOCK1$ sudo iptables -A KNOCK3 -m recent --name AUTH2 --remove
$ sudo iptables -A KNOCK3 -p tcp --dport 6357 -m recent --name AUTH3 --set -j DROP
$ sudo iptables -A KNOCK3 -j KNOCK1
The Passed Chain
$ sudo iptables -A PASSED -m recent --name AUTH3 --remove
$ sudo iptables -A PASSED -p tcp --dport 22 -j ACCEPT
$ sudo iptables -A PASSED -j KNOCK1
Configuring WALL Chain
$ sudo iptables -A WALL -m recent --rcheck --seconds 30 --name AUTH3 -j PASSED
$ sudo iptables -A WALL -m recent --rcheck --seconds 10 --name AUTH2 -j KNOCK3
$ sudo iptables -A WALL -m recent --rcheck --seconds 10 --name AUTH2 -j KNOCK2
$ sudo iptables -A WALL -j KNOCK1
Once you are done with all the commands your iptables list should look something like this:
I have the script of all the commands and have passed the port numbers as arguments from the command line. This allows me to change the port number without changing the code. You can find the whole script here.
Now, it is time to test the firewall. First you need to find a way to hit the ports correctly. For this you can use nmap, hping or anything you are comfortable with. With nmap you can do something like this.
nmap -Pn --host_timeout 201 --max-retries 0 -p portNumber sshServer
You can simply hit the 3 ports with this line:
$ for x in 5678 5984 6357; do nmap -Pn --host-timeout 201 --max-retries 0 -p $x 172.31.75.10 && sleep 1; done
$ ssh firstname.lastname@example.org
As you can see from the image above that you are able to connect to other system using ssh service.
Choice of port numbers is an important aspect when it comes to implementing port knocking. Many times people often use predictable port numbers like 1111, 2222 and 3333; or 1234, 2345, 3456. Fair amount of time and good guess work allows hackers to intrude the first layer of defense. Most of the time it is found that the sequence of port numbers increases. To increase obscurity one can select the port numbers in order like: 6547, 2385, 5875; or something similar.
Other use of Port Knocking
I often leave my system with python’s HTTP file server open. This allows me to remotely access my files from anywhere. One way to make sure no one else gets access to my content to implement port knocking in the firewall of my system. Another way of doing this is adding a layer of authentication, like this. I will recommend to use both the security mechanisms increasing the privacy of your content.
In order to make the firewall rules persistent we need to install another package in the server:
$ sudo apt-get install iptables-persistent
$ sudo service iptables-persistent start
Additional links for further reading