Setting up port knocking

Posted under: Security
- Tagged: port knocking, SSH

What we want to do is to be able to knock on three ports in the correct sequence, when we do the firewall will open for an SSH connection from our IP for a period (let's start with 10 seconds) and then after the period, automatically close behind us.

First let's get knockd installed, on Debian it's as simple as

sudo apt-get install knockd

Now we'll get a warning saying that knockd is disabled and not starting, and that we need to edit /etc/default/knockd. But before we do that, let's set up the sequence knockd looks for and the command it runs. This is held in /etc/knockd.conf

[options] 
        UseSyslog

[openSSH]
        sequence    = 7000,8000,9000
        seq_timeout = 5
        command     = /sbin/iptables -A INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
        tcpflags    = syn

[closeSSH]
        sequence    = 9000,8000,7000
        seq_timeout = 5
        command     = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
        tcpflags    = syn

This is the default config that knockd comes with. First let's set up the sequence we'll knock to have knockd open the port. You can really set it to almost any three ports you want as long as they're not one of the commonly used ports that may be used by another process (a list is available on Wikipedia here). But since I was thinking of making a quick Python program to knock the ports for me let's go ahead and pick some random ones, I'll use random.org to get three random numbers between 1024 and 65535 (the last port number). For the sake of example, let's use the port numbers 59577, 63663, 10717, none of which appear to be common ports. Go back to /etc/knockd.conf and comment out the section for [closeSSH] and change it to match the below.

[openSSH]
        sequence      = 59577,63663,10717
        seq_timeout   = 5        
        tcpflags      = syn
        start_command = /sbin/iptables -A INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
        cmd_timeout   = 10
        stop_command  = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT

Here we're setting the sequence, the period (seq_timeout) in which we have to knock the ports as well as the command that will be run and then the stop command which will be run after the timeout (cmd_timeout). The above example assume you have SSHD listening on port 22, if not change the --dport section to the port number SSHD is listening on.

Now before knockd will start, we'll have to enable it (as we were told in the error when it was installed). Edit /etc/default/knockd for editing and look for the START_KNOCKD=0 line and change it to START_KNOCKD=1. Slightly below that, you may need to specify the network interface. In this case of this server, eth0 was not correct and knockd would not start automatically. To find the interface you can use ip addr list and find the interface with your public facing IP listed, in my case it's venet0. So we'll go back into /etc/default/knockd, uncomment the line KNOCKD_OPTS="-i eth0" and remove eth0 and replace it with venet0 (or whichever interface you found above).

Now knockd should start.

For port knocking, on Linux you can use the program knock which comes with knockd or in our case, let's use a quick Python program for it.

#!/usr/bin/python3

import argparse
import socket

def knock(host, port):

    conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    conn.setblocking(0)
    try:
        conn.connect((host, port))
    except socket.error:
        print('Knocked on {} {}'.format(host, port))

def knock_range(host, ports):

    for port in ports:
        knock(host, port)

if __name__ == '__main__':

    arg_parser = argparse.ArgumentParser(description='Simple Port Knocker.')
    arg_parser.add_argument('Host', help='The host you wish to knock on')
    arg_parser.add_argument('Ports', nargs='*', type=int, help='List of ports to knock on')
    args = arg_parser.parse_args()
    host = args.Host
    ports = args.Ports
    if ports:
        knock_range(host, ports)
    else:
        print('No ports listed, try listing some ports to knock on')

Usage of the above would be:

./portKnock.py defestri.org  59577 63663 10717

Scrutiny

Posted under: Security
- Tagged: fail2ban, Scrutiny, SSH

I've since updated the auth-log parser into the application Scrutiny. This Python3 application parses auth.log and fail2ban.log log files and puts the details into the database. I've also since updated the Defestri site itself to read this data and display it on a page here. Maybe next I'll look at pulling some more general trend information from the data, for example common user names tried, common source address blocks, that sort of thing. With information such as common source address blocks it will be possible to drop all traffic from the offending subnet to reduce the number of break-in attempts coming in.

Revisiting the auth log

Posted under: Security
- Tagged: SSH, fail2ban

After about a month since my last update let's check our logs and see how the slightly stricter security measures are holding up.

Dec  2 11:28:43 defestri sshd[26001]: Invalid user administraator from 217.153.86.163
Dec  2 11:28:43 defestri sshd[26003]: Invalid user administrador from 217.153.86.163
Dec  2 11:28:44 defestri sshd[26005]: Invalid user administranto from 217.153.86.163
Dec  2 11:28:44 defestri sshd[26007]: Invalid user administrate from 217.153.86.163
Dec  2 11:28:45 defestri sshd[26009]: Invalid user administrateur from 217.153.86.163
Dec  2 20:31:43 defestri sshd[26261]: Invalid user lukas from 198.61.179.140
Dec  2 20:31:44 defestri sshd[26263]: Invalid user ottomar from 198.61.179.140
Dec  2 20:31:45 defestri sshd[26265]: Invalid user pankraz from 198.61.179.140
Dec  2 20:31:46 defestri sshd[26267]: Invalid user lucas from 198.61.179.140
Dec  2 20:56:05 defestri sshd[27794]: Invalid user pomelnic from 184.106.189.106
Dec  2 20:56:51 defestri sshd[27854]: Invalid user ram from 184.106.189.106
Dec  2 20:56:52 defestri sshd[27856]: Invalid user jake from 184.106.189.106
Dec  2 21:09:22 defestri sshd[27868]: Invalid user admin from 64.235.53.4

As we can see there's still plenty of log in attempts coming in, although slightly less than last time. We can see how fail2ban is going by having a quick look at it's log.

2013-12-02 11:28:45,147 fail2ban.actions: WARNING [ssh] Ban 217.153.86.163
2013-12-02 20:31:46,725 fail2ban.actions: WARNING [ssh] Ban 198.61.179.140
2013-12-02 20:56:53,357 fail2ban.actions: WARNING [ssh] Ban 184.106.189.106

So we can see it has successfully banned the three IPs that have attempted to break in three or more times in the log.

Out of curiosity I've written a parser to parse the auth.log and fail2ban.log and see not only the IPs (and the countries) these attempts are coming from but also the usernames they're trying to log in with. The files are up on my Github here.

The auth-log parser script basically looks through the log, pulling out attempts for the last month, checks the location of the attempts using ipinfodb's API then uses jinja2 to output it into a HTML file.

From here I'd like to get the auth-log parser to run as a cron job every month, parse the log in attempts from the previous month and output it to a page visible on this site. On the side of security however, next up I think I'll get fail2ban to monitor it's own log, banning people who have been banned multiple times before. Then we should hopefully have a more secure server.

UPDATE: I've seen changed the parser into the program Scrutiny which does output into the database.