Rate this page

Flattr this

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:

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:

Any number of separate, named lists can be kept. In this instance there will be three:

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:

  1. Accept traffic that should not be subject to port knocking
  2. Handle state 0 (waiting for the first knock)
  3. Handle state 1 (waiting for the second knock)
  4. Handle state 2 (waiting for the third knock)
  5. Handle state 3 (waiting for a connection)
  6. 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:

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:

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:

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:

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

Tags: firewall | iptables