System: Implementing Port Knocking with knockd
We're revisiting the perennial problem of how to secure SSH (and other ports) from automated attacks which grow more persistent each year.
Of course, if you have a private network, or know where everyone is going to be connecting from, you can create a white-list of ip addresses, but that doesn't work with dynamic addresses or travellers.
In previous articles we've presented Fail2Ban as a decent first line of defence. And this can be augmented by shutting down the standard SSH port (:22) and instead using another port (:ssh2).
Unfortunately now in 2018 the bots are doing port scans to identify non-standard SSH ports and including them in their attack vector. This is where port knocking comes in.
What is Port Knocking?
With port knocking you effectively close all your SSH ports and open them only if the client first 'knocks' on a pre-selected port, or sequence of ports, and then only long enough for the client to establish a connection.
For simplicity we've opted for knockd which is a basic port-knocking daemon and client compatible with iptables. Installation on Debian/Ubuntu is straight-forward:
$ sudo apt-get -u install knockd
Configuration files can be found at /etc/knockd.conf and /etc/default/knockd and it can be controlled using systemd:
$ sudo systemctl start knockd.service
In the configuration you will see options for logging and for setting up port/protocol combinations for opening and closing ports via iptables. More on that below.
Along with knockd comes knock which is a client for knocking on ports, but you can also use curl or, in many cases, even a standard web browser.
Custom iptables configuration
We have made some changes to the default configuration to fit into our existing firewall. We redirect SSH(2) traffic to a separate chain and use knockd to add/remove entries from that chain.
In the following 'ssh2' is code for our alternative SSH port. Pick your own.
The relevant portion of our firewall script now looks something like:
... # ssh2 port accessible through knockd iptables -N ssh-allow-knocked-ips iptables -A ssh-allow-knocked-ips -j DROP iptables -A INPUT -p tcp --dport ssh2 -j ssh-allow-knocked-ips ...
This creates a new chain ssh-allow-knocked-ips which parses
$ sudo iptables -vnL INPUT
Chain INPUT (policy DROP)
pkts bytes target prot opt in out source destination
0 0 ssh-allow-knocked-ips tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:ssh2
To start with all traffic is simply DROPped.
$ sudo iptables -vnL ssh-allow-knocked-ips Chain ssh-allow-knocked-ips (1 references) pkts bytes target prot opt in out source destination 0 0 LOG all -- * * 0.0.0.0/0 0.0.0.0/0 LOG flags 0 level 7 prefix "did not knock: " 0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0
The LOG line is something we just added for testing, but you may want to keep it:
$ sudo iptables -I ssh-allow-knocked-ips -j LOG --log-prefix "did not knock: " --log-level 7
Custom knockd configuration
We now modify /etc/knockd.conf with our own knocking sequence (port XXXX followed by YYYY) and iptables commands to add/remove rules in our ssh-allow-knocked-ips chain:
[options] UseSyslog [opencloseSSH] sequence = XXXX:tcp,YYYY:tcp tcpflags = syn seq_timeout = 10 command = iptables -C ssh-allow-knocked-ips -s %IP% -j ACCEPT || iptables -I ssh-allow-knocked-ips -s %IP% -j ACCEPT cmd_timeout = 60 stop_command = iptables -C ssh-allow-knocked-ips -s %IP% -j ACCEPT && iptables -D ssh-allow-knocked-ips -s %IP% -j ACCEPT
The iptables -C (check) commands are to prevent duplicate entries.
We're assuming the firewall has a rule to pass RELATED,ESTABLISHED traffic once an initial connection has been made. If not you may want to use separate knocks for [openSSH] and [closeSSH] and leave the window open in between.
Activating the daemon
The final step is to edit /etc/default/knockd:
# control if we start knockd at init or not # 1 = start # anything else = don't start # PLEASE EDIT /etc/knockd.conf BEFORE ENABLING START_KNOCKD=1 # command line options KNOCKD_OPTS="--verbose"
And then start the daemon:
$ sudo systemctl start knockd.service
Depending on your log settings output could appear in /var/log/daemon.log, /var/log/knockd.log or journalctl.
Now in order to connect to ssh2 you will need to first complete the 'knock' sequence. In this case ports XXXX and then YYYY.
You won't get any response from the knock, but if you check iptables you should see your ip address appearing in the relevant chain for 60 seconds.
An in that time you should be able to make an SSH connection.
Keeping the door open
If disconnections are a problem, try updating ClientAliveInterval (server side) and ServerAliveInterval (client side) to keep the connection open.
Otherwise you can use a simple shell command:
$ watch -n61 knock -v hostname XXXX YYYY
This will send a 'knock' just after the window closes - after 60 seconds in our example configuration.
Unfortunately at this time knockd 0.7-1 has no IPv6 support.
knockd not starting on boot
A bug affecting Debian 9 means that knockd does not start automatically on server boot. You can identify the problem using:
# systemctl is-enabled knockd.service static
The fix is to patch /lib/systemd/system/knockd.service by adding an [Install] section and then run:
# systemctl enable knockd.service Synchronizing state of knockd.service with SysV service script with /lib/systemd/systemd-sysv-install. Executing: /lib/systemd/systemd-sysv-install enable knockd Created symlink /etc/systemd/system/knockd.service → /lib/systemd/system/knockd.service. # systemctl is-enabled knockd.service enabled
Related Articles - Fail2Ban
- System Monitoring the fail2ban log
- System Optimising your Fail2Ban filters
- System Implementing Port Knocking with knockd
- System Using systemd to bind fail2ban to nftables
- System Fail2Ban 0.8.3 Howto
- System Blocking FTP Hacking Attempts
- System Using a Fail2Ban Jail to Whitelist a User
- System fail2ban and sendmail
- System fail2ban and iptables