Web knocking (better approach to port knocking)

Port knocking is a great approach to get rid of the bots trying to brute force via SSH. The only problem is that not all the users may have tools (or skills) to perform this action.

On the other hand, most systems have (or should have) fail2ban installed. This tool shines for its configuration flexibility. So, why not to implement port knocking which can be done with a simple browser?

The idea behind the approach consists in making an HTTP request to a known URL and because of that port 22 opens for the IP which made the request.

Nginx configuration

First of all, we configure HTTP server (Nginx in this case) for simply logging all requests to a given URL. We don’t even need a correct response (404 is perfectly fine).

server {
        listen 80;
        server_name example.com;

        access_log /var/log/nginx/example.com.log access_log;
}

As you may see I listen on port 80 logging all incoming requests. All of them will return 404, but that’s OK since all of them are going to leave a trace in a log file.

Netfilter configuration

As with a port knocking approach its Netfilter who decides who is going to be allowed in. Let’s create a chain:

iptables -N letmein-ssh

For that chain the default action (if there is no hit) should be RETURN, so I append the rule:

iptables -A letmein-ssh -j RETURN

After that, we should redirect to this chain all the incoming packets which come to port 22.

iptables -I INPUT -p tcp -m tcp --dport 22 -j letmein-ssh

This rule should be at the top of other rules which may affect port 22. The explanation is quite simple, every packet that comes in via port 22 is introduced in the chain letmein-ssh, if there is no hit the packet is simply returned to the original chain which is INPUT. It’s important to note that the default action for INPUT chain should be DROP, so the packet is dropped after all.

Fail2Ban configuration

Now, it’s time to configure fail2ban to pick up the traces we are interested in. In this case, the trace will look like:

81.40.123.29 - - [17/Jan/2017:11:58:27 +0000] "GET /ssh HTTP/1.1" 404 144 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36" "-"

Time to configure fail2ban filter:

# Fail2Ban filter for letmein-ssh

[INCLUDES]
# I don’t need any include for this

[Definition]
failregex = ^<HOST> -.*"GET /ssh.*$
ignoreregex =

Now comes the magic. We should reverse ban action, I mean, when filter hits the trace it should allow connection to port 22 from the IP.

# Fail2Ban configuration file
#

[INCLUDES]

# No need to include anything

[Definition]

# Option:  actionstart
# Notes.:  command executed once at the start of Fail2Ban.
# We setup a basic RETURN rule with this one
# Values:  CMD
#
actionstart = iptables -A letmein-ssh -j RETURN

# Option:  actionstop
# Notes.:  command executed once at the end of Fail2Ban
# Values:  CMD
# 
actionstop = iptables -F letmein-ssh

# Option:  actioncheck
# Notes.:  command executed once before each actionban command
# We don’t need to check anything
# Values:  CMD
# 
actioncheck =

# Option:  actionban
# This command actually inserts rule
#
actionban = iptables -I letmein-ssh -s <ip> -j ACCEPT

# Option:  actionunban
# Notes.:  command executed when unbanning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Once again, we do the magic here so actually this command disables an IP from accessing SSH.
# Values:  CMD
#
actionunban = iptables -D letmein-ssh -s <ip> -j ACCEPT

Finally I configure jail for what I need:

[letmein-ssh]
enabled  = true
filter   = letmein-ssh
action   = allow-iptables-letmein
logpath  = /var/log/nginx/example.com.log access_log;
bantime  = 120
maxretry = 1
findtime = 60

That's all. Now, before connecting via SSH to server the client need to issue an HTTP request to http://example.com/ssh.