I am preparing a post about tools and tips for Tor node operators and, as it frequently happens, trying to put together and write up what I have learned led me to discover new stuff. In particular, I have learned a new technique for hiding services from port scanners and scripts-kiddies called “port knocking“. I decided I wanted to implement it for my node: here you can find a guide on how to do so.
Port knocking for the impatient
If you are going to copy-paste these scripts change the ports numbers, otherwise this is useless. Also, remember that port knocking is not a valid authentication method and should not be used to provide authentication.
- change your SSH server configuration in
/etc/ssh/sshd_config
to run SSH on a non-standard port: sshd_config.patch; - copy iptables rules. These rules set up a knock sequence on ports
23456
,34567
,45678
,56789
with UDP packets that lead to opening port4992
where SSH should be listening: iptables.rules; - install iptables rules: install rules;
- verify that everything is working as expected;
- knocking script: knock.sh.
Port knocking: theory and code
What is port knocking?
Wikipedia defines port knocking quite clearly:
Port knocking is a method of externally opening ports on a firewall by generating a connection attempt on a set of prespecified closed ports. Once a correct sequence of connection attempts is received, the firewall rules are dynamically modified to allow the host which sent the connection attempts to connect over specific port(s).
(source: Wikipedia)
The analogy with knocking actual doors is quite strong: the port for a given service – SSH, for example – is kept closed unless the server receives a known sequence of packets. Each packet of the sequence is called a knock. If a valid, pre-established knock sequence is received then the firewall opens the port for the given service for a limited amount of time.

It should be noted, and this is stressed on almost all the guides that I have read, that port knocking is not a secure authentication method and should never be used as the only security method to protect your services. In particular, if you want to securely login remotely to your machine, you should use SSH with (RSA) key authentication.
You can set up port knocking with the help of a dedicated service like knockd, which allows the easy setup of complicated knocking sequences, however it is also possible to configure port knocking using only iptables. While the configuration may be more complicated, using only iptables has the advantages of not requiring additional software and of not depending on a service that could cause additional problems of its own (e.g. if the daemon stops working for any reason you are locked outside). There are other tools like knockknock that focus on simplicity.
Configuration
Set up key-based-only SSH authentication on a non-standard port
If you have just created a VPS, you will probably have root access with root
being the only user available. The first thing to do is to secure your SSH server. To do so, you need to:
- make the SSH server listen on a non-standard port (through this example it will be port
4992
, you can choose any random high port); - disable password login, leaving us only with key-based authentication;
- create a new user
myuser
withuseradd myuser
; - increase the size of SSH v1 key (this is probably obsolete, but can’t hurt);
- disable root login;
- disable X11 forwarding;
- disable Linux PAM;
- Turn off reverse DNS lookups;
At the end, you should change the following configuration values in /etc/ssh/sshd_config
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# What ports, IPs and protocols we listen for Port 4992 # Lifetime and size of ephemeral version 1 server key ServerKeyBits 2048 # Authentication: PermitRootLogin no AuthorizedKeysFile %h/.ssh/authorized_keys X11Forwarding no UsePAM no # Disable reverse DNS lookups. # See: # https://unix.stackexchange.com/questions/56941/ # what-is-the-point-of-sshd-usedns-option UseDNS no |
(view this code on GitHub as a patch with respect to the stock SSH server configuration)
It can be a good idea also to regenerate the ssh host keys using an higher number of bits (2048
for the RSA key, and 512
for the ECDSA key).
Copy the file authorized_keys
file over to myuser
, this will allow you key-based logins.
1 2 3 4 5 6 7 8 9 10 |
root@myserver:~# cp /root/.ssh/authorized_keys /home/myuser/.ssh/authorized_keys root@myserver:~# chown myuser:myuser /home/myuser/.ssh/authorized_keys root@myserver:~# su myuser myuser@myserver:/root$ cd ~/.ssh myuser@myserver:~/.ssh$ chmod 600 authorized_keys myuser@myserver:~/.ssh$ ls -lha total 12K -rw------- 1 myuser myuser 395 Nov 14 14:33 authorized_keys drwxr-xr-x 8 myuser myuser 4.0K Apr 25 17:53 .. drwxr-xr-x 2 myuser myuser 4.0K Nov 14 14:33 . |
Please note that the permission of both /home/myuser
and /home/myuser/.ssh
should be set to 755
otherwise the server will discard them.
Restart you SSH server:
1 |
root@myserver:~# service ssh restart |
and test if everything is working correctly logging in from another terminal.
iptables rules
Below you can find the rules to configure port knocking on your node for SSH. The following rules establish that a sequence of 4 knocks on ports 23456
, 34567
, 45678
, 56789
with UDP packets is needed before opening SSH on port 4992.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
############################################################################## ## ## iptables rules for a Tor node with port knocking hiding SSH. ## ## These rules are based on a combination of: ## https://raw.githubusercontent.com/torservers/ ## server-config-templates/master/iptables.test.rules ## and ## https://www.debian-administration.org/article/ ## 268/Multiple-port_knocking_Netfilter/IPtables_only_implementation ## ## For further info see: ## https://balist.es/blog/2016/05/03/configure-port-knocking-for-a-tor-node ## ## Some parameters you may want to change: ## * Ports on which to knock: ## 1. KNOCK1: 23456 ## 2. KNOCK2: 34567 ## 3. KNOCK3: 45678 ## 4. KNOCK4: 56789 ## * SSH port: 4992 ## * Tor ports: ## * DirPort: 80 ## * ORPort: 443 ## ############################################################################## *raw -A PREROUTING -j NOTRACK -A OUTPUT -j NOTRACK COMMIT *filter ## Set default rules and reset packet and byte counts ## See: https://www.krenger.ch/blog/iptables-accept-00-brackets/ :INPUT ACCEPT [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] ## Allows all loopback (lo0) traffic and drop all traffic to 127/8 that ## doesn't use lo0 -A INPUT -i lo -j ACCEPT ## allow incoming SSH for testing: # -A INPUT -p tcp --dport 2222 -j ACCEPT ## DirPort, ORPort (optional: Webserver) -A INPUT -p tcp --dport 80 -j ACCEPT -A INPUT -p tcp --dport 443 -j ACCEPT ## Allow several ICMP types ## http://www.oregontechsupport.com/articles/icmp.txt -A INPUT -p icmp -m icmp --icmp-type host-unreachable -j ACCEPT -A INPUT -p icmp -m icmp --icmp-type port-unreachable -j ACCEPT -A INPUT -p icmp -m icmp --icmp-type fragmentation-needed -j ACCEPT -A INPUT -p icmp -m icmp --icmp-type source-quench -j ACCEPT -A INPUT -p icmp --icmp-type echo-request -m limit --limit 2/s -j ACCEPT -A INPUT -p icmp --icmp-type echo-request -j DROP -A INPUT -p icmp -j DROP ## Define port knocking -N INTO-PHASE2 -A INTO-PHASE2 -m recent --name PHASE1 --remove -A INTO-PHASE2 -m recent --name PHASE2 --set -A INTO-PHASE2 -j LOG --log-prefix "INTO PHASE2: " -A INTO-PHASE2 -j DROP -N INTO-PHASE3 -A INTO-PHASE3 -m recent --name PHASE2 --remove -A INTO-PHASE3 -m recent --name PHASE3 --set -A INTO-PHASE3 -j LOG --log-prefix "INTO PHASE3: " -A INTO-PHASE3 -j DROP -N INTO-PHASE4 -A INTO-PHASE4 -m recent --name PHASE3 --remove -A INTO-PHASE4 -m recent --name PHASE4 --set -A INTO-PHASE4 -j LOG --log-prefix "INTO PHASE4: " -A INTO-PHASE4 -m recent --rcheck --name PHASE4 -j LOG --log-prefix "(OPEN PORT 4992) - " -A INTO-PHASE4 -j DROP ## if you want to kock using tcp packets uncomment this line: # -A INPUT -m recent --update --name PHASE1 ## Define knocking sequence: knock on ports and then open SSH port for 5 seconds -A INPUT -p udp --dport 23456 -m recent --set --name PHASE1 -A INPUT -p udp --dport 34567 -m recent --rcheck --name PHASE1 -j INTO-PHASE2 -A INPUT -p udp --dport 45678 -m recent --rcheck --name PHASE2 -j INTO-PHASE3 -A INPUT -p udp --dport 56789 -m recent --rcheck --name PHASE3 -j INTO-PHASE4 -A INPUT -p tcp --dport 4992 -i eth0 -m recent --rcheck --seconds 5 --name PHASE4 -j ACCEPT ## drop non-established TCP -A INPUT -p tcp --syn -j DROP ## reject connections on port 4992 -A INPUT -p tcp --dport 4992 -m state --state NEW -j REJECT ## allows all udp in, but avoids conntrack COMMIT |
(view this code on GitHub)
Knocking script
The following script knocks on the ports specified above with UDP packets.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
#!/bin/bash ############################################################################## # # Port knocking script # Based on: # http://www.microhowto.info/howto/ # implement_port_knocking_using_iptables.html # # This script knocks on the four ports defined below with UDP packets: # * Ports on which to knock: # 1. KNOCK1: 23456 # 2. KNOCK2: 34567 # 3. KNOCK3: 45678 # 4. KNOCK4: 56789 # and connect to an SSH server listening on port 4992 # # Usage: # # ./knock.sh && ssh --p4492 myuser@myserver # # # please note that if you are using anotherserver as a proxy you can do: # # ssh myproxyserver 'bash -s' < knock.sh && ssh myserver # # Provided that you have configured ssh properly, e.g. putting the following # in your ~/.ssh/config: # # --- # Host myserver # Hostname 123.123.123.123 # User myuser # Port 4992 # ProxyCommand ssh myproxyserver -W %h:%p # --- # ############################################################################## # Set "bash strict mode" # See: # http://redsymbol.net/articles/unofficial-bash-strict-mode/ set -euo pipefail IFS=$'\n\t' # myserver IP address HOST='123.123.123.123' # ports to knock PORT1=23456 PORT2=34567 PORT3=45678 PORT4=56789 echo "KNOCK1" echo -n "*" | nc -q1 -u $HOST $PORT1 echo "KNOCK2" echo -n "*" | nc -q1 -u $HOST $PORT2 echo "KNOCK3" echo -n "*" | nc -q1 -u $HOST $PORT3 echo "KNOCK4" echo -n "*" | nc -q1 -u $HOST $PORT4 echo "CONNECT" |
(view this code on GitHub)
Applying the rules
These rules were tested on a Debian 7.10 (wheezy) machine with iptables v. 1.4.14. They should work on any system with a reasonably recent version of iptables.
I recommend trying them first on a machine you can safely lose/reinstall if needed because it is at least theoretically possible that you lock yourself out when trying this [1]To be honest: it took me a couple of tries before getting this configuration right. The first time I locked myself out of a server and only then (sic et simpliciter) I realized that it was better to … Continue reading.
To apply the rules above you can save them in a file iptables.rules
and execute the following command (as root)
1 |
iptables-restore < iptables.rules |
Testing
In the following we will fire up a backup SSH server and look at the connection logs of the server.
Set up a backup SSH server
To be safe, you can set up a second SSH server listening on port 2222
, just in case:
- create a directory called
~/backup_ssh/
; - copy the configuration file
/etc/ssh/sshd_config
in~/backup_ssh/sshd_config
; - copy the
authorized_keys
file in~/backup_ssh/
; - create a folder named
keys
in~/backup_ssh/
and generate new server keys; - update
~/backup_ssh/sshd_config
changing:- the port number to
2222
; - the location of server keys to
./keys/ssh_host_rsa_key
,./backup_ssh/keys/ssh_host_dsa_key
and./backup_ssh/keys/ssh_host_ecdsa_key
; - the
AuthorizedKeysFile
to./authorized_keys
;
- the port number to
- uncomment line
43
from the rules above to open port2222
in the firewall. - in a screen/tmux session launch the backup ssh server :
1 |
/usr/sbin/sshd -D -d -f ~/backup_ssh/sshd_config |
When it is up, the server should say the following:
1 2 3 4 5 |
[snip] debug1: Bind to port 2222 on 0.0.0.0. Server listening on 0.0.0.0 port 2222. debug1: Bind to port 2222 on ::. Server listening on :: port 2222. |
Note that this server will die on disconnect, so you need to start it each time you use it.
Monitor the logs
You can monitor the connection attemps from your local machine to your server using tcpdump
. Suppose that the local machine has IP 111.111.111.111 and the remote server has ip 123.123.123.123. Launch the following command monitors all connection from the specified source ip:
1 |
tcpdump -n src host 111.111.111.111 |
A successful port knocking and following SSH connection will look like this:
1 2 3 4 5 6 7 8 9 10 11 |
root@myserver:~# tcpdump -n src host 111.111.111.111 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes 21:47:35.658923 IP 111.111.111.111.60177 > 123.123.123.123.23456: UDP, length 1 21:47:35.672566 IP 111.111.111.111.32950 > 123.123.123.123.34567: UDP, length 1 21:47:35.689304 IP 111.111.111.111.57382 > 123.123.123.123.45678: UDP, length 1 21:47:35.703951 IP 111.111.111.111.40499 > 123.123.123.123.56789: UDP, length 1 21:47:36.107370 IP 111.111.111.111.41315 > 123.123.123.123.4992: Flags [S], seq 845295113, win 29200, options [mss 1460,sackOK,TS val 1115560618 ecr 0,nop,wscale 7], length 0 21:47:36.118310 IP 111.111.111.111.41315 > 123.123.123.123.4992: Flags [.], ack 737630169, win 229, options [nop,nop,TS val 1115560621 ecr 795754], length 0 21:47:36.136414 IP 111.111.111.111.41315 > 123.123.123.123.4992: Flags [.], ack 40, win 229, options [nop,nop,TS val 1115560625 ecr 795759], length 0 21:47:36.147193 IP 111.111.111.111.41315 > 123.123.123.123.4992: Flags [P.], seq 0:41, ack 40, win 229, options [nop,nop,TS val 1115560628 ecr 795759], length 41 |
We can also monitor the kernel logs for iptables log messages:
1 |
tailf /var/log/kern.log |
A successful port knocking and following SSH connection will look like this:
1 2 3 4 5 6 7 |
root@myserver:~# tailf /var/log/kern.log Apr 28 21:47:14 myserver kernel: [ 3461.273041] device eth0 left promiscuous mode Apr 28 21:47:15 myserver kernel: [ 3462.667561] device eth0 entered promiscuous mode Apr 28 21:47:35 myserver kernel: [ 3482.584734] INTO PHASE2: IN=eth0 OUT= MAC=00:00:00:00:00:00:00:00:00:00:00:00:00:00 SRC=111.111.111.111 DST=123.123.123.123 LEN=29 TOS=0x00 PREC=0x00 TTL=59 ID=7017 DF PROTO=UDP SPT=32950 DPT=34567 LEN=9 Apr 28 21:47:35 myserver kernel: [ 3482.601458] INTO PHASE3: IN=eth0 OUT= MAC=00:00:00:00:00:00:00:00:00:00:00:00:00:00 SRC=111.111.111.111 DST=123.123.123.123 LEN=29 TOS=0x00 PREC=0x00 TTL=59 ID=7020 DF PROTO=UDP SPT=57382 DPT=45678 LEN=9 Apr 28 21:47:35 myserver kernel: [ 3482.616103] INTO PHASE4: IN=eth0 OUT= MAC=00:00:00:00:00:00:00:00:00:00:00:00:00:00 SRC=111.111.111.111 DST=123.123.123.123 LEN=29 TOS=0x00 PREC=0x00 TTL=59 ID=7023 DF PROTO=UDP SPT=40499 DPT=56789 LEN=9 Apr 28 21:47:35 myserver kernel: [ 3482.617084] (OPEN PORT 4992) - IN=eth0 OUT= MAC=00:00:00:00:00:00:00:00:00:00:00:00:00:00 SRC=111.111.111.111 DST=123.123.123.123 LEN=29 TOS=0x00 PREC=0x00 TTL=59 ID=7023 DF PROTO=UDP SPT=40499 DPT=56789 LEN=9 |
From the client side, this is how a successful knocking and connection looks like:
1 2 3 4 5 6 7 8 |
user@mylocalmachine$ ./knock.sh && ssh myserver KNOCK1 KNOCK2 KNOCK3 KNOCK4 CONNECT Last login: Tue May 3 00:23:50 2016 from 111.111.111.111 myuser@myserver:~$ |
Installing
If everything works you can get rid of your backup SSH server (remmeber to comment back line 43
above) and make the iptables rules permanent. We save all the rules in /etc/iptables.rules
, we also download a script iptables-restore that re-applies the iptables rules when restarting the network:
1 2 3 4 5 6 7 |
# Install iptables rules to be applied when interface is brought up cd /etc/ iptables-save > iptables.rules chmod 600 iptables.rules cd /etc/network/if-pre-up.d/ wget https://raw.githubusercontent.com/torservers/server-config-templates/master/iptables-restore chmod +x iptables-restore |
Caveat emptor
As pointed out above and in many other venues (see the red box in Arch’s wiki page on port knocking) you should be warned:
Port knocking should be used as part of a security strategy, not as the only protection. That would be a fragile security through obscurity. In case of SSH protection, see SSH keys for a strong method that can be used along with port knocking.
Thus port knocking as only the limited benefit of obscurity. In practice, with port knocking am attacker scanning your machine should first need to guess the correct knocking sequence before discovering your service. It should be noted, though, that this is true only if you choose your port knocking sequence at random. To do so you should change the port numbers, including the port on which you SSH server is listening. You can generate random numbers using a service like random.org. I repeat, do not use the port sequence used in this example for live configurations permanently.
In the case of an attacker that is able to monitor the connections between you and your server the protection offered by port knocking is even more limited. Why is so? As the author of knockknock points out:
The problem with the original concept [of port knocking] was that if your port sequence was observed by passive eavesdropping, it was easily replayable.
that is saying that anyone that could monitor the connections between you and your server can learn which is the knocking sequence that you are using and which is the port where your SSH server is listening and simply knock himself. For this reason a strong authentication method, such as RSA keys, must be used to provide strong authentication.
You may wonder at this point what good can come from techniques like port knocking or the widespread advice of not running SSH on standard ports. If they do not provide security in a strict sense, why bother changing the settings? For this I would like to share this good advices from the tor-relay mailing list. Running SSH on non-standard ports and/or using port knocking can have 3 main advantages:
- Many brute-force attackers will only bother trying port 22 (the default port for SSH). Just moving SSH to a non-standard port will shelter you from these attackers.
- Using non-standard ports keeps the log files clean, which allows you to focus on the (relative small) number of login attempts the machine gets.
- If OpenSSH (or one of its dependencies) should ever be subject to a severe security issue, plenty of folks would immediately
start scanning and exploiting the Internet. A non-standard port (or port knocking) would likely give you a grace period which would allow you to shut down SSH or take other measures. In other words these measures try to restrict the perimeter of exposure of your services an machines, as the creator of knockknock puts it:
Why Is This Even Necessary?
Because you are running network services with security vulnerabilities in them. Again, you are running network services with security vulnerabilities in them. If you’re running a server, this is almost universally true. Most software is complex. It changes rapidly, and innovation tends to make it more complex. It is going to be, forever, hopelessly, insecure. Even projects like OpenSSH that were designed from the ground-up with security in mind, where every single line of code is written with security as a top priority, where countless people audit the changes that are made every day — even projects like this have suffered from remotely exploitable vulnerabilities. If this is true, what hope do the network services that are written with different priorities have?
The name of the game is to isolate, compartmentalize, and expose running services as little as possible.
Furthermore some other solutions developed on the initial idea of port knocking, such as knockknock, provide countermeasures to some of the limitations above.
Finally, no security-related solution can be relied upon until it has reviewed by a significant number of expert n the field. I do not consider myself an expert in networking and/or the manipulation of iptables. If you find errors lurking in the rules above or improvements that could be made you are invited to provide them in the comments.
References
↑1 | To be honest: it took me a couple of tries before getting this configuration right. The first time I locked myself out of a server and only then (sic et simpliciter) I realized that it was better to fire up a new server for testing. |
---|