Snap! Websites
An Open Source CMS System in C++
Lately, I have been reading about port knocking and saw many posts saying that it is not that safe and therefore rather useless since it adds the burden of knocking before you can connect with a tool such as ssh (for which port knocking is most often implemented).
The fact is that isn't true. It is perfectly safe if you use the proper tools to setup the port knocking and execute it. In fact, wikipedia talks about it and clearly tells you that port knocking with just 3 ports generates some 240+ trillion possibilities which pretty much no hacker can hope to ever break.
The snapwebsites implementation prevents you from using ports in order, and the final destination port (i.e. 100, 200, 300 is forbidden and if you set this up for SSH, port 22 is also forbidden, by default, you can always update your SSH server to listen on a different port). This reduces the number of possibilities to just 140+ trillion possibilities. The iplock project includes a tool that allows you to determine that number (since it is really large, it takes time and memory, but if you are patient you'll get the exact number).
The following are the final rules that you want to get. Here I present the rules with a single static IP address (10.10.10.10) as if you owned that IP and did not want anyone else to connect to the computer (so port knocking + static IP address). If you remove the `-s <ip>`, then you only allow port knocking.
-A INPUT -i eth0 -p tcp -m state --state ESTABLISHED,RELATED -m tcp ! --syn -j ACCEPT -A INPUT -m recent --rcheck --seconds 10 --name knock3 -m recent --remove --name hacker -A INPUT -m recent --rcheck --seconds 10 --name knock3 -i eth0 -p tcp -s 10.10.10.10 --dport 22 -m tcp --syn -j ACCEPT -A INPUT -m recent --rcheck --seconds 10 --name knock2 -m recent --set --name knock3 -p tcp --dport 200 -m tcp --syn -A INPUT -m recent --remove --name knock2 -A INPUT -m recent --rcheck --seconds 10 --name knock1 -m recent --set --name knock2 -p tcp --dport 300 -m tcp --syn -A INPUT -m recent --remove --name knock1 -A INPUT -m recent --set --name knock1 -p tcp --dport 100 -m tcp --syn -A INPUT -p tcp --dport 22 -j DROP
The first rule allows established/related connections to receive packets.
The last rule blocks any other attempts to connect to port 22.
The rules in between allow port knocking on port 100 (knock1).
When a knock was received on port 100, a knock can happen on port 300 (knock2).
Finally, after a knock on port 300, you are allowed to knock on port 200 (knock3).
This last knock opens access to port 22.
The `--seconds` option allows you to define a timing of how long the port knocking remains open. If you try to knock the next port after 10 seconds, it fails. If you try to connect to port 22 after the third knock after you wanted more than 10 seconds, it also fails.
There are other rules that are not shown here that will automatically add IP addresses of users who fail the port knocking (and attempt to connect to other unopened ports) to the hacker set. This hacker set is used to automatically block users for a much longer time. In other words, if the hacker fails the port knocking, his IP gets blocked for say 1h, making it really hard to ever find the correct sequence in any reasonable amount of time. Our system allows for whitelisting your IP address if it is static. We also have scripts that will allow you to manage a dynamic ipset with a dynamic IP address.
The iplock project comes with a set of rules that are used to generate complex iptables rules (or at least many of them). One issue with the iptables is the product that using multiple parameters can generate.
As an example, the rule allowing access to port 22 above can be assigned 10 static IP addresses (i.e. one per administrator) and you may have access through two different ethernet port. This means you need 10 x 2 = 20 rules to allow all the combinaisons safely. In most likelihood, if you write those manually, you're not going to feel like writing that many rules. One way to manage all the IPs is to add them to an ipset. As a result, you only have to define two rules and one ipset... and you still have to manage the ipset.
With iplock, you enter parameters in various variables in a configuration files and the rest is pretty much automatic.
As an example here are the default two iplock rules for SSH:
# Allow admins to connect through SSH [rule::ssh_by_admins] section = early_content after = established_input before = unwanted_call chains = INPUT condition = '"${admin_ips}${knock_ports}" != ""' source_interfaces = ${admin_interfaces} sources = ${admin_ips} destination_port = ${ssh_port} knocks = ${knock_ports} protocols = tcp state = new action = ACCEPT # Forbid all others [rule::block_ssh] section = early_content after = ssh_by_admins chains = INPUT protocol = tcp destination_port = ${ssh_port} action = DROP
As we can see, we have variables:
The second rule (block_ssh) is not conditional. It ensures that the user gets blocked at that point if it is trying to connect to the `${ssh_port}`.
The first rule has that reference to `${knock_ports}`. This is where we can very simply add the list of ports and protocol (tcp and udp at the moment) to allow for a knock. Here is an example of such ports as presented in the iptables rules above:
knock_ports = 100, 300, 200
By default, the protocol is set to TCP. You can also use UDP like so:
knock_ports = tcp:100, udp:300, tcp:200
In this later case, you want to know port 300 with a UDP packet. UDP can be tricky since you cannot be sure that the packet is received. Use with caution. If you often have flaky Internet connections, it's probably not a good idea. On a LAN, it is likely to work 99.99999% of the time.
Just defining that one variable generates all the rules you've seen above. Very practical! Trying to do that manually is very much prone to a lot of mistakes.
Some port knocking implementations use a knock listener: a service which listens on the given ports and when the proper sequence is received by that service, it adds a rule in your firewall to let your IP address connect to port 22.
This means those ports have to be accessible. Hackers can detect those ports. If you use three such ports, the combinasions are 3! = 6 possibilities. Very easy for a hacker to find the correct knocking sequence.
Plus, older versions would open port 22 without adding your IP address as the source IP. In other words, anyone would be able to connect to port 22 while opened in this way, adding absolutely no safety at all from the port knocking. The knock listener would eventually remove the opened port after some number of seconds. The fact is that hackers have robots trying to connect all the time. If it happens to be opened at the time the hacker's bot is awake and ready to check your server IP address, you're going to be out of luck.
Our firewall based implementation, on the other hand, does not offer hackers a way to determine whether their knocking did anything (the knocking on port 100, 200, or 300 above does not return any information to the hacker any different from all the other ports that are blocked).
Next port 22 is always defined in our firewall, but it is accessible only after you went through the port knocking sequence. Something that a service based solution could not know and act on in any proper way.
Snap! Websites
An Open Source CMS System in C++