Implement port knocking using iptables
Tested on |
Debian (Etch, Lenny, Squeeze) |
Ubuntu (Hardy, Intrepid, Jaunty, Karmic, Lucid, Maverick, Natty, Oneiric, Precise, Quantal) |
Objective
To implement port knocking using iptables, such that inbound connections are permitted only if the client ‘knocks’ on a particular sequence of ports beforehand.
Background
Port knocking is a technique used to protect network services which must be accessible from the public Internet but are not intended for public use. It works by blocking access to the service unless the client first ‘knocks’ by sending a short sequence of packets to a particular set of ports in a particular order. The content of the packets is unimportant.
By itself this would be a very poor method of authentication because the required packet sequence is trivially observable by anyone located on the path between the client and the server. What it can provide is a first line of defence to protect against vulnerabilities in the primary authentication mechanism.
The port knocking system itself will be fully exposed to the Internet, so to achieve anything it must be less susceptible to vulnerabilities than the service it seeks to protect. This is why the validity of the port knock sequence depends only on the port numbers and not on the packet content: if the payload is discarded by the server then there is little scope for placing anything malicious inside it.
There are two commonly-used methods for implementing port knocking on Linux-based systems:
- using a daemon such as knockd, or
- using iptables alone.
Advantages of an iptables-based solution are that it builds upon an existing well-proven codebase, much of which would probably be exposed to the Internet regardless of whether it is used for port knocking. The only new code that must be written is in the form of iptables rules, which is considerably safer than using a general-purpose programming language such as C. Finally, because there is no daemon required there is no risk of it terminating.
Scenario
Suppose that www.example.com
is a publicly accessible web server running HTTP on TCP port 80. The only other externally accessible service is SSH running on TCP port 22, used for remote administration. You wish to protect the SSH service using port knocking, without affecting public access to the HTTP service.
You have chosen to use a knock sequence consisting of three UDP datagrams directed to ports 12345, 23456 and 34567 in that order. You do not wish to impose any specific time limit on this sequence, however any incorrect UDP datagram should cause the port knocking system to reset to its initial state. Traffic from different source addresses should be processed separately.
Method
Overview
The netfilter modules included in the stock Linux kernel do not provide direct support for port knocking, but they do contain all of the building blocks needed to implement it. The most important of these is the recent
module, which has the ability to:
- maintain a list of recent packets that satisfy a given condition, and
- make decisions according to whether the number of recorded packets from a given source address is above or below a given threshold.
Any number of separate, named lists can be kept. In this instance there will be three:
-
KNOCK1
identifies source addresses that have performed the first knock, but not the second or third. -
KNOCK2
identifies source addresses that have performed the first and second knocks, but not the third. -
KNOCK3
identifies source addresses that have performed the first, second and third knocks but have not yet established a connection.
It follows that no source address should appear in more than one list, so there are four possible states in which any given source address can be:
State | List name | Description |
---|---|---|
0 | — | waiting for first knock |
1 | KNOCK1 |
waiting for second knock |
2 | KNOCK2 |
waiting for third knock |
3 | KNOCK3 |
waiting for connection |
For the purpose of explanation the required ruleset has been split in to six parts:
- Accept traffic that should not be subject to port knocking
- Handle state 0 (waiting for the first knock)
- Handle state 1 (waiting for the second knock)
- Handle state 2 (waiting for the third knock)
- Handle state 3 (waiting for a connection)
- Selecting the appropriate handler chain
The ruleset has also been listed as a single script which is ready for use, save for the choice of port numbers.
Accept traffic that should not be subject to port knocking
As is the case for most firewall configurations, once a decision has been made to accept the first packet of a connection then any further packets forming part of the same connection should be allowed to pass unhindered:
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
It is not usually appropriate to block traffic from the loopback interface, and you should certainly not do so indisciminately:
iptables -A INPUT -s 127.0.0.0/8 -j ACCEPT
The same is arguably true for ICMP traffic. Blocking may provide some minor security benefits, but in most situations it is likely to cause more inconvenience to legitimate users and administrators than it would to a potential attacker:
iptables -A INPUT -p icmp -j ACCEPT
Finally, public services should not be subject to port knocking otherwise they would not be public. In this case the host is a web server, so at a minimum it needs to accept TCP connections on port 80:
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
These rules should appear at or near the start of the input chain, prior to the rules which select one of the port knock states.
Handle state 0 (waiting for the first knock)
When the server is waiting for the first knock:
- if the packet is the first knock then progress to state 1;
- otherwise remain at state 0.
Either way, any received traffic should be dropped:
iptables -N STATE0 iptables -A STATE0 -p udp --dport 12345 -m recent --name KNOCK1 --set -j DROP iptables -A STATE0 -j DROP
Handle state 1 (waiting for the second knock)
When the first knock has been received, and the server is waiting for the second knock:
- if the packet is the second knock then progress to state 2;
- if the packet is another first knock then remain at state 1;
- otherwise, regress to state 0.
As above, any received traffic should be dropped.
The chain for state 0 already contains rules for handling a first knock. Reusing this chain avoids stating the port number at multiple locations in the ruleset:
iptables -N STATE1 iptables -A STATE1 -m recent --name KNOCK1 --remove iptables -A STATE1 -p udp --dport 23456 -m recent --name KNOCK2 --set -j DROP iptables -A STATE1 -j STATE0
Handle state 2 (waiting for the third knock)
When the first and second knocks have been received, and the server is waiting for the third knock:
- if the packet is the third knock then progress to state 3;
- if the packet is another first knock then regress to state 1;
- otherwise, regress to state 0.
As above, any received traffic should be dropped:
iptables -N STATE2 iptables -A STATE2 -m recent --name KNOCK2 --remove iptables -A STATE2 -p udp --dport 34567 -m recent --name KNOCK3 --set -j DROP iptables -A STATE2 -j STATE0
Handle state 3 (waiting for a connection)
When all three knocks have been received, and the server is waiting for a connection:
- if the packet is the start of an inbound TCP connection to the relevant port then accept it;
- if the packet is another first knock then regress to state 1;
- otherwise, regress to state 0.
It is only necessary to accept the first packet of the connection in this manner because there is already a rule to accept traffic that forms part of an established connection. As above, other traffic should be dropped:
iptables -N STATE3 iptables -A STATE3 -m recent --name KNOCK3 --remove iptables -A STATE3 -p tcp --dport 22 -j ACCEPT iptables -A STATE3 -j STATE0
Select the appropriate handler chain
Having defined handler chains for all four states, it is necessary to place rules in the input chain to invoke the appropriate handler chain. This can be done by testing whether the source address of the current packet is in any of the three lists:
iptables -A INPUT -m recent --name KNOCK3 --rcheck -j STATE3 iptables -A INPUT -m recent --name KNOCK2 --rcheck -j STATE2 iptables -A INPUT -m recent --name KNOCK1 --rcheck -j STATE1 iptables -A INPUT -j STATE0
The complete ruleset
Here is a complete iptables configuration presented in the form of a shell script which implements the port-knocking system described above. It will overwrite any existing rules in filter
table. Prior to installation you should replace the listed port numbers with ones of your choosing. You may also want to change or add to the set of ports on which connections are accepted.
#!/bin/sh iptables -F iptables -X iptables -Z iptables -N STATE0 iptables -A STATE0 -p udp --dport 12345 -m recent --name KNOCK1 --set -j DROP iptables -A STATE0 -j DROP iptables -N STATE1 iptables -A STATE1 -m recent --name KNOCK1 --remove iptables -A STATE1 -p udp --dport 23456 -m recent --name KNOCK2 --set -j DROP iptables -A STATE1 -j STATE0 iptables -N STATE2 iptables -A STATE2 -m recent --name KNOCK2 --remove iptables -A STATE2 -p udp --dport 34567 -m recent --name KNOCK3 --set -j DROP iptables -A STATE2 -j STATE0 iptables -N STATE3 iptables -A STATE3 -m recent --name KNOCK3 --remove iptables -A STATE3 -p tcp --dport 22 -j ACCEPT iptables -A STATE3 -j STATE0 iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -s 127.0.0.0/8 -j ACCEPT iptables -A INPUT -p icmp -j ACCEPT iptables -A INPUT -p tcp --dport 80 -j ACCEPT iptables -A INPUT -m recent --name KNOCK3 --rcheck -j STATE3 iptables -A INPUT -m recent --name KNOCK2 --rcheck -j STATE2 iptables -A INPUT -m recent --name KNOCK1 --rcheck -j STATE1 iptables -A INPUT -j STATE0
Testing
The port knock sequence can be sent using any utility capable of transmitting UDP datagrams to specific port numbers. Options include netcat
:
#!/bin/sh echo -n "*" | nc -q1 -u www.example.com 12345 echo -n "*" | nc -q1 -u www.example.com 23456 echo -n "*" | nc -q1 -u www.example.com 34567 ssh www.example.com
and hping3:
#!/bin/sh hping3 www.example.com --udp -c 1 -p 12345 hping3 www.example.com --udp -c 1 -p 23456 hping3 www.example.com --udp -c 1 -p 34567 ssh www.example.com
Alternatives
knockd
An alternative to the above method is to use knockd, which is a dedicated and highly configurable port-knock server. The configuration file that it uses is considerably simpler and more readable than the equivalent for iptables because it need only specify what behaviour is required and not how to achieve it. This can be a significant advantage for complex configurations.
Security considerations
As noted above, port knocking provides no protection against an attacker able to monitor traffic between the client and the server. For this reason it should only be used as a supplement to an effective primary authentication mechanism, not as a substitute.
Three knocks allows for approximately 48 bits of entropy, but only if the port numbers are chosen randomly. It would be challenging but not necessarily impossible to execute a remote brute-force attack against a keyspace that large. If greater security is required then options include increasing the number of knocks, or limiting the rate at which inbound UDP datagrams are processed.
Normally a case can be made for rejecting unwanted traffic in order to facilitate troubleshooting, but in this instance that might provide the feedback needed to perform a timing attack. Dropping is therefore recommended, at least for packets of the type used in the port knock sequence.
Port knocking is susceptibile to a denial of service attack against specific client addresses. If this becomes a problem then an immediate remedy is to connect from a different address. It may also be beneficial to enable the --rttl
option of the recent
module.
Further reading
- Port Knocking, Arch Linux wiki (ArchWiki)
- Matous Jan Fialka, Multiple-port knocking Netfilter/IPtables only implementation, Debian Administration, October 2005