Decred behind Portknocking

Using portknocking makes it virtually impossible for an attacker to discover the service running on that port. For example, for portknocking that requires the user to hit 3 different ports in 5 seconds, it would be necessary to test 65535^3 ports.

Decred behind Portknocking
Decred behind Portknocking

By Marcelo Martins - January 10, 2020

Portknocking is a technique used to hide the existence of a service running on a server. Portknocking requires that a specific port combination receive traffic in order for the service port to be opened.

Using portknocking makes it virtually impossible for an attacker to discover the service running on that port. For example, for portknocking that requires the user to hit 3 different ports in 5 seconds, it would be necessary to test 65535^3 ports.

WARNING! If your Internet access is being monitored, the censor may notice this type of network traffic. A partial solution would be to open ports from another IP address.

To learn more about knockd, read https://linux.die.net/man/1/knockd.

The following steps were performed on a Debian 9 64-bit.

a) Install knockd from the default repository.

$ sudo apt-get install knockd

b) Edit /etc/default/knockd.conf file and set parameter START_KNOCKD=1 so that the service will start automatically at the next operating system startup.

The next example shows knockd configuration on file /etc/knockd.conf for sshd service.

$ cat /etc/knockd.conf

[options]
    UseSyslog

[openSSH]
 sequence = 7532,2357,7235
 seq_timeout = 5
 command = /sbin/iptables -A INPUT -s %IP% -p tcp --dport 22 -m comment --comment "Knockd: SSH" -j ACCEPT
 tcpflags = syn

[closeSSH]
 sequence = 7235,2357,7532
 seq_timeout = 5
 command = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -m comment --comment "Knockd: SSH" -j ACCEPT
 tcpflags = syn

If there are any rules in iptables that allow or deny traffic that will be controlled by Portknocking this rule must be deleted. To block all traffic that is not explicitly allowed use the iptables chain policy:

$ sudo iptables -P INPUT DROP

If you have made any changes to iptables rules or policies, the ‘iptables-save’ command should be used to write the rules permanently.

Start the service manually.

$ sudo service knockd start

Whenever the portknocking rules change, you must restart the service.

$ sudo service knockd restart

Create the following scripts on the client device that will be used to open services ports protected by Portknocking. In the following examples telnet was used for knocking, but knockd comes with a knock application that allows you to choose the port and protocol in the port: protocol format. The project site contains clients for other platforms.

$ cat dcrd_ssh_open.sh

telnet $DCRD_IP 7532
telnet $DCRD_IP 2357
telnet $DCRD_IP 7235
$ cat dcrd_ssh_close.sh

telnet $DCRD_IP 7235
telnet $DCRD_IP 2357
telnet $DCRD_IP 7532

At the end, run the script and connect to the port that was opened.

$ ./dcrd_ssh_open.sh

As explained in section 1, if the censor monitors traffic, one option is to open the port from another IP address. In this case, the iptables command cannot include the source address (-s% IP%) as a traffic restriction or will have to keep a fixed IP address in the configuration. But there are more practical alternatives to try to bypass the censor, such as setting up other sequences with common ports, for example.

The following examples hide the SSH TCP/22 port, but could be used to hide the VPN or TCP port of Decred components.

  • Interface: The default interface where knockd expects packets is “eth0”, but can be changed with this directive.
  • seq_timeout: Determines the maximum wait time for all packets in the sequence.
  • tcpflags: Determines the flags that should be marked on TCP packets. knockd will ignore packets that do not have flags selected, but will not invalidate the entire sequence forcing the client to start over. Multiple flags can be separated with commas (eg tcpflags = syn, ack, urg) and can be explicitly negated with “!” (eg: tcpflags = syn,! ack).
  • command: the iptables command that will be executed when the client hits the correct sequence of knocks.
$ cat /etc/knockd.conf

[options]
    UseSyslog
    Interface = ens33
    
[openSSH]
 sequence = 80,443,21
 seq_timeout = 29
 command = /sbin/iptables -A INPUT -s %IP% -p tcp --dport 22 --syn -m comment --comment "Knockd: SSH" -j ACCEPT
 tcpflags = syn
$ cat /etc/knockd.conf

[options]
    UseSyslog
    Interface = ens33
    
[openSSH]
 sequence = /etc/knockd_ssh_sequences
 seq_timeout = 29
 command = /sbin/iptables -A INPUT -s %IP% -p tcp --dport 22 --syn -m comment --comment "Knockd: SSH" -j ACCEPT
 tcpflags = syn

In this example other directives are also used:

  • start_command: the iptables command that will be executed when the client hits the correct sequence of knocks. The %IP% variable will be replaced with the knocker IP IP address.
  • cmd_timeout: Timeout between execution of start_command and stop_command. It is optional and only required if stop_command is used.
  • stop_command: the iptables command that will be executed when the seconds of cmd_timeout are over. The %IP%variable will be replaced with the knocker IP IP address. It is optional.
$ cat /etc/knockd.conf

[options]
    UseSyslog
    Interface = ens33
    
[opencloseSSH]
    one_time_sequences = /opt/knockd_ssh_sequences
    seq_timeout   = 29
    tcpflags      = syn
    start_command = /usr/sbin/iptables -A INPUT -s %IP% -p tcp --dport 22 --syn -m comment --comment "Knockd: SSH" -j ACCEPT
    cmd_timeout   = 25
    stop_command  = /usr/sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 --syn -m comment --comment "Knockd: SSH" -j ACCEPT
    
[openclosedcrwallet]
    one_time_sequences = /opt/knockd_dcrwallet_sequences
    seq_timeout   = 29
    tcpflags      = syn
    start_command = /usr/sbin/iptables -A INPUT -s %IP% -p tcp --dport 9109 --syn -m comment --comment "Knockd: dcrwallet" -j ACCEPT
    cmd_timeout   = 25
    stop_command  = /usr/sbin/iptables -D INPUT -s %IP% -p tcp --dport 9109 --syn -m comment --comment "Knockd: dcrwallet" -j ACCEPT

The third example uses predefined port sequences, which prevents the censor from easily guessing this behavior.

File /opt/knockd_ssh_sequences contains the port sequences that will be used. Each time a string is used, it will be invalidated with a comment (#) at the beginning of the line. This comment will replace the first character of the line, so you must leave a blank space at the beginning of each line.

$ cat /opt/knockd_ssh_sequences

 80:tcp,443:tcp,53:udp
 21:tcp,123:udp,995:tcp
 53:udp,23:tcp,135:tcp

This example assumes that the same knockd_ssh_sequences file also exists on the client.

$ cat dcrd_open.sh

# Author: Marcelo Martins (stakey.club)
#! /bin/sh -

# Example: dcrd_open.sh [line number] [server IP address] [seq_file]

KNOCK_BIN=knock
SEQ_FILE="knockd_ssh_sequences"
SLEEP_MAX=14
SRV_IP="11.12.13.14"

if [ -z $1 ]; then
        echo "Line number must be provided."
        exit 1
fi
if [[ $(( $1 % 1 )) -eq $1 ]]; then
        echo "Argument \"$1\" is not a number."
        exit 1
fi
if [ ! -z $2 ]; then
        SRV_IP=$2
fi
if [ ! -z $3 ]; then
        SEQ_FILE=$3
fi
SEQ=`head -"$1" $SEQ_FILE | tail -1`
PORT1=`echo $SEQ | awk -F "," '{print $1}'`
PORT2=`echo $SEQ | awk -F "," '{print $2}'`
PORT3=`echo $SEQ | awk -F "," '{print $3}'`
if [[ -z $PORT1 || -z $PORT2 || -z $PORT3 ]]; then
        echo "Unable to extract port:protocol from file $SEQ_FILE, line was [$SEQ]."
        exit 1
fi
SLEEP1=$(( RANDOM % $SLEEP_MAX ))  # do not use RANDOM for simulations
SLEEP2=$(( RANDOM % $SLEEP_MAX ))  # or crypto, it's flawed

echo "Knocking on server's door... at $SRV_IP, ports $PORT1 $PORT2 $PORT3"
$KNOCK_BIN $SRV_IP $PORT1
echo "Sleeping for $SLEEP1 seconds."
sleep $SLEEP1
echo "Knock, knocking on server's dooor... at $SRV_IP, ports $PORT1 $PORT2 $PORT3"
$KNOCK_BIN $SRV_IP $PORT2
echo "Sleeping for $SLEEP2 seconds."
sleep $SLEEP2
echo "Knock, knock, knocking on server's dooooor... at $SRV_IP, ports $PORT1 $PORT2 $PORT3"
$KNOCK_BIN $SRV_IP $PORT3
echo "We're done knocking on server's door. The door must be open."

To prevent attackers from trying to portscan or guess the Portknocking sequence, you can configure firewall rules to temporarily block IP addresses that show this behavior.

a) First is created the blacklist that will keep for 180 seconds, for example, the addresses sent to it. This rule drops all packets coming from addresses that are in the list.

$ sudo iptables -A INPUT -m recent --name blacklist_180 --rcheck --seconds 180 -m comment --comment "IP Blacklisted for 180 sec" -j DROP

b) Next we create the chain TCP_SYN on the first line. The second line directs the TCP_SYN chain to all TCP packets with this flag checked. It is in the TCP_SYN chain that it will be checked if, after 3 packets with the SYN flag marked per second, we continue to receive a number of packets exceeding the limit (1/s), as shown in the third line. If this happens, the chain follows processing and sends the IP address to the blacklist as determined by the fourth line. If not, the third rule returns processing to the INPUT chain.

$sudo iptables -N TCP_SYN
$sudo iptables -A INPUT -p tcp --syn -j TCP_SYN
$sudo iptables -A TCP_SYN -m limit --limit 1/s --limit-burst 3 -m comment --comment "Limit TCP SYN rate" -j RETURN
$ sudo iptables -A TCP_SYN -m recent --name blacklist_180 --set -m comment --comment "Blacklist source IP address" -j DROP