Creating knockd with iptables

UPDATE 2017-MAR-12: Removed references to fail2ban, following up a massive change in ubuntu’s fail2ban package that broke this knock. Now it’s not dependent on fail2ban, but seamlessly aligns behind it.

Having a knock daemon in front of your sensitive service is always a good idea. It won’t protect against a direct, planned attack, but it will avert random port scanners looking for vulnerabilities at random IP addresses.

Note that here I was focusing on the simplest knock daemon only, one that opens up a port if a valid sequence of connection attempts arrive. So far I’ve been using an ancient knockd for that (see debian/ubuntu repo), however, it being a userspace netfilter, all traffic passed through it and was consuming a huge amount of CPU (cca. 45 minutes a day). Since this is such a simple task that even an iptables rule set could handle it, I found this disproportionate and decided to dust off my iptables skills.

There are a lot more powerful knock daemons available too. For example, one that hides a one time pad in an ICMP payload. Or encrypt the current hour+minute+some secret with a secret RSA key and send that in an ICMP ping. And then you could combine this with some innocent-looking traffic, e.g. hiding the above in a regular looking web traffic. These are only the tip of the iceberg of what you can do, just what I could think of while typing this paragraph. Here, we will focus on the simplest knock ‘daemon’ possible, in order to preserve resources and to keep it painfully simple.

I’ve looked into this on google as well, of course, and boiled it down that I want:

  • ipv4 and ipv6 support;
  • tcp/udp/… support;
  • drop client if wrong sequence was hit;
  • integrate it with fail2ban-ssh;
  • don’t interfere with normal traffic (no port ranges reserved for knockd, …);
  • make no performance impact on normal traffic.

Below I made iptables chains for a knock sequence of 3, with a maximum of 10 second delay in between. How this works is a list is made for each state (using the iptables recent module). When the first port is hit, we add the source IP to the knock1 list. When an IP is in the knock1 list, and the second port in the sequence is hit, we add it to the knock2 list, and so on, until the source IP makes it into the knockp list (p for passed), which would simply allow accessing port 22 (SSH). If a client makes any other interaction in the watched ports that does not match the next port in the sequence, it is simply removed from all lists and it will have to start over again. So no brute forcing.

Here we only react on a specific port range only (51000 to 55000 in the below example), so that other traffic from the same IP would normally have no effect on our knocking. Also, I’ve made a knock chain and only route ‘knocking’ traffic trough our rules so that regular traffic would be almost unaffected. By using ACCEPT target, any service that actually would make use of these ports is not affected in any way. I happen to use high port numbers to avoid getting too many packets through these rules for no reason. Also, iptables’ recent module has a (configurable) limit of IP addresses on each list, which, if exhausted within the 10-second timeframe, would make knocking impossible.

So here it goes:

#!/bin/bash

function knock() {
IPTABLES=$1
LOCALNET=$2

# create/flush chains
$IPTABLES -N knock
$IPTABLES -N knock1
$IPTABLES -N knock2
$IPTABLES -N knockp

$IPTABLES -F knock
$IPTABLES -F knock1
$IPTABLES -F knock2
$IPTABLES -F knockp

## INPUT
# accept established connections (don't use conntrack here, as it terminates ssh connections after 5-15 mins, and is generally very resource-inefficient)
$IPTABLES -A INPUT -p tcp ! --syn --dport 22 -j ACCEPT
# accept ssh from local network
$IPTABLES -A INPUT -p tcp -s "$LOCALNET" --dport 22 -j ACCEPT

# if 'knock passed', go to pass-through chain
$IPTABLES -A INPUT -m recent --name knockp --rcheck --seconds 10 --reap -j knockp

# if within knock range, jump to knock chain
$IPTABLES -A INPUT -p tcp --dport 51000:55000 -j knock
$IPTABLES -A INPUT -p udp --dport 51000:55000 -j knock

# drop the rest of ssh initiation traffic
$IPTABLES -A INPUT -p tcp --dport 22 --syn -j DROP

## KNOCK
# route incoming packets by source IP state
$IPTABLES -A knock -m recent --name knock1 --rcheck --seconds 10 --reap -j knock1
$IPTABLES -A knock -m recent --name knock2 --rcheck --seconds 10 --reap -j knock2

# set new state on sequence hit
$IPTABLES -A knock -p udp --dport 51001 -m recent --name knock1 --set -j ACCEPT
$IPTABLES -A knock -p tcp --dport 51001 -m recent --name knock1 --set -j ACCEPT

$IPTABLES -A knock1 -m recent --name knock1 --remove
$IPTABLES -A knock1 -p udp --dport 51002 -m recent --name knock2 --set -j ACCEPT
$IPTABLES -A knock1 -p tcp --dport 51002 -m recent --name knock2 --set -j ACCEPT

$IPTABLES -A knock2 -m recent --name knock2 --remove
$IPTABLES -A knock2 -p udp --dport 51003 -m recent --name knockp --set -j ACCEPT
$IPTABLES -A knock2 -p tcp --dport 51003 -m recent --name knockp --set -j ACCEPT

# passed chain: allow ssh
$IPTABLES -A knockp -p tcp --dport 22 --syn -j ACCEPT
}
### v4
knock iptables 10/24

### v6
knock ip6tables 2001:981:a07b:1::/64

I’ve extracted the actual knock rules creation in a bash function, so that I could easily repeat this for v4 and v6 without added maintenance and testing. Comments should offer reasonable explanation of the rules.

Since this box is behind a firewall/router already (on the internal network), I did not add extra firewall rules here, only the ones necessary for knock-proofing the ssh port.

Hope this helped. Feel free to leave a comment.

DIY: external antenna for AVM Fritzbox 7340

I have this AVM Fritzbox, as a courtesy of my provider, xs4all. Since this thing has built-in wifi-n, with guestlan support, I’ve started using it to connect up my wireless devices.

But here comes the dilemma. You want to put the fritzbox as close as possible to the incoming telephone (vdsl2) cable as possible, so that you have the best internet reception. On the other hand, you also want to have good wifi coverage in your house. See, the upper floor has almost no coverage (using a macbook pro 2014, it jumps between no connection and 5mpbs physical speeds).

I needed something to extend this range a little bit.

One way to do this is to leave fritzbox where it is, but connect external antennas that can be placed farther away, and as an added bonus, have a better reception than the built-in ones. So I thought I give it a try: replace the built-in antennas.

Internally, the Fritzbox uses an (de-facto industry standard) IPEX antenna connection, and has 2 antennas. So all I had to do is get 2 RP-SMA – IPEX cables and 2 good RP-SMA antennas, and I was good to go. I’ve picked up mine here and here.

So let’s get into the box. Unscrew the 2 visible screws using a torx bit, and the 2 invisible ones hidden under the lower pads:

IMG_20150208_115943

Next, use a flat screwdriver to unhook the central holder:

IMG_20150208_120018

Open up and remove the 2 internal antennas:

IMG_20150208_113932

IMG_20150208_114622

Be careful while working with the IPEX connectors, they are tiny and fragile. Best use a flat screwdriver to carefully pry them up from the PCB, a tiny push at a time, then switch to the other side to keep the connector straight.

Now remove the whole PCB (it’s simply set on top of the bottom part of the plastic case), and drill holes for the external RP-SMA connectors, then screw them in:

IMG_20150208_115251

IMG_20150208_115837

I’ve used 6mm wooden drill bits for this. When you put the hole in the lower back part of the case (of course you can put it anywhere else, I put it there following traditional placement only – so it looks like it was manufactured this way).

Hook up the IPEX antenna connectors. Here you want to be extra careful, move around the connector on the socket while trying to apply some force, as the IPEX connectors are fragile and are supposed to get stuck on top of the socket, so it’s quite hard to put on, but also easy to deform the connector. It will probably take a few minutes, be patient, move the connector around the socket, and slowly increase force.

IMG_20150208_114850

Once done, re-assemble the case, and you’re done!

IMG_20150208_120131

Was it worth it? Well, I paid 10 euros for a pair of cables and 10dBi antennas, which I can re-use in other wifi access points too (manufacturers like to equip their routers with tiny-teeny crappy antennas). Now I have between 20-40mbps physical speed on the upper floor, and most importantly, it’s stable. So it won’t make wonders, but does offer a remedy.

You might also want to google for antenna extension cable, attenuation and positioning of the external antennas – they matter quite a lot. Bottom line is, position the 2 antennas ortogonally, with their sides towards the expected location of the clients, and don’t use more than 3-5 meters of antenna cables (you need the ‘standard’ wifi antenna cables, those are RP-SMA), as cable attenuation will ruin what you’ve won with the higher gain external antennas.