Saturday 24 March 2007

port-knocking with iptables only

port-knocking protection is a method that you open a specific port only after you attempt to connect some other ports, in a right order, just like you open a door with right sequence of knocking. It adds another layer of protection to a server. It is useful to allow only authorised access, for example, a firewall or a CVS server on which ssh accesses are authorised to a limited number of users. It is not suitable for servers running public services like web server, mail server, etc.

Most implementation of port knocking protections involve a dedicate a daemon that keeps monitoring the system logs, in order to detect the knocking sequence.

Here I describe a method to implement the port knocking protection, inspired by A. P. Lawrence's article. It use iptables only. On linux, iptables is used for firewalling (v2.2 and older use ipchains), so with this implementation, no separate dedicated daemon is run.

The key feature of iptables to be used here is the table called “recent”, which was introduced for later version of iptables. With some old version of linux distros, you may need to update iptables package. More information about iptables module recent can be found here.

The advantage of this implementation is that it is elegant (no additional software installed) and lightweight (no additional daemon running). The limitation is that this is only suitable for a fixed mapping. But generally this is good enough, it is especially effective for firewalling out the attacking of ssh worms, script kiddies, port scanning or the like.

Here is a sample script:

#!/bin/bash
# $IPTABLES rules to be run by /etc/rc.d/rc.local
#
# 08 Feb, 2005 -- implement an elegant port-knocking mechanism to restrict ssh access for this machine
#
#### where is iptables binary?
IPTABLES=/sbin/iptables
#
#### define the knocking port set here
# I take my telephone number and break it down into three pieces for easy memory.
PK_PORT0=1223 # 1st knocking port
PK_PORT1=569 # 2nd knocking port
PK_PORT2=715 # 3rd knocking port
#
#### Set some sensible kernel params that may already be there
#### Assume necessary iptable modules are loaded.
/bin/echo "0" > /proc/sys/net/ipv4/icmp_echo_ignore_all
/bin/echo "1" > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
/bin/echo "0" > /proc/sys/net/ipv4/conf/all/accept_source_route
/bin/echo "0" > /proc/sys/net/ipv4/conf/all/accept_redirects
/bin/echo "1" > /proc/sys/net/ipv4/icmp_ignore_bogus_error_responses
/bin/echo "1" > /proc/sys/net/ipv4/conf/all/log_martians
/bin/echo "1" > /proc/sys/net/ipv4/ip_forward
#
#### Flushing tables
$IPTABLES -F
$IPTABLES -t nat -F
$IPTABLES -X
$IPTABLES -t nat -X
#
#### Set all policies
$IPTABLES -P INPUT DROP
$IPTABLES -P FORWARD DROP
$IPTABLES -P OUTPUT DROP
#
#### Being paranoid to avoid intrution during the excutation of this script
$IPTABLES -I INPUT 1 -i ! lo -j DROP
$IPTABLES -I FORWARD 1 -i ! lo -j DROP
$IPTABLES -I OUTPUT 1 -o ! lo -j DROP
$IPTABLES -A INPUT -i lo -j ACCEPT
$IPTABLES -A OUTPUT -o lo -j ACCEPT
#
#### Filter out some obviously proofed packets from internet
$IPTABLES -A INPUT -s 10.0.0.0/8 -i $EXT_IF -j DROP
$IPTABLES -A INPUT -s 127.0.0.0/8 -i $EXT_IF -j DROP
$IPTABLES -A INPUT -s 172.16.0.0/12 -i $EXT_IF -j DROP
$IPTABLES -A INPUT -s 192.168.0.0/16 -i $EXT_IF -j DROP
$IPTABLES -A FORWARD -s 10.0.0.0/8 -i $EXT_IF -j DROP
$IPTABLES -A FORWARD -s 127.0.0.0/8 -i $EXT_IF -j DROP
$IPTABLES -A FORWARD -s 172.16.0.0/12 -i $EXT_IF -j DROP
$IPTABLES -A FORWARD -s 192.168.0.0/16 -i $EXT_IF -j DROP
#
## allow all outgoing packets
$IPTABLES -A OUTPUT -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPTABLES -A OUTPUT -o eth0 -j ACCEPT
#
#### allow some ICMP packets for friendly probes
#### it also gives a hacker indication of the machine existence though, :)
$IPTABLES -N icmp-packets
$IPTABLES -A icmp-packets -p icmp --icmp-type redirect -j DROP
$IPTABLES -A icmp-packets -p icmp --icmp-type echo-request -j ACCEPT
$IPTABLES -A icmp-packets -p icmp --icmp-type echo-reply -j ACCEPT
$IPTABLES -A icmp-packets -p icmp --icmp-type destination-unreachable -j ACCEPT
$IPTABLES -A icmp-packets -p icmp --icmp-type source-quench -j ACCEPT
$IPTABLES -A icmp-packets -p icmp --icmp-type time-exceeded -j ACCEPT
$IPTABLES -A icmp-packets -p icmp --icmp-type parameter-problem -j ACCEPT
#
#### SYN flood protection, which is typical for DDOS attack
$IPTABLES -N syn-flood
$IPTABLES -A syn-flood -m limit --limit 1/s --limit-burst 4 -j RETURN
$IPTABLES -A syn-flood -j DROP
#
#### Log packet fragments just to see if we get any, and deny them too
#### This may be not necessary, thus commented out.
#$IPTABLES -A INPUT -i eth0 -f -j DROP
#
#### These chains are for the port-knocking protection
$IPTABLES -N port-knocking
$IPTABLES -N knocking-okey
$IPTABLES -N knocking-oops
#### ====== actual contents of port knocking chains =======
#### Using module recent to implement a simple and elegant port knocking mechanism
#### First we make sure the port knocking order is correct.
#### Note: --update option updates the hitcount number, while --rcheck does not;
#### Neither of them changes the last-seen time.
$IPTABLES -A port-knocking -p tcp --dport $PK_PORT0 -m recent --rcheck --hitcount 1 -j knocking-oops
$IPTABLES -A port-knocking -p tcp --dport $PK_PORT0 -m recent --set -j REJECT --reject-with host-prohib
$IPTABLES -A port-knocking -p tcp --dport $PK_PORT1 -m recent --rcheck --hitcount 2 -j knocking-oops
$IPTABLES -A port-knocking -p tcp --dport $PK_PORT1 -m recent --update --hitcount 1 -j REJECT --reject-with host-prohib
$IPTABLES -A port-knocking -p tcp --dport $PK_PORT2 -m recent --rcheck --hitcount 3 -j knocking-oops
$IPTABLES -A port-knocking -p tcp --dport $PK_PORT2 -m recent --update --hitcount 2 -j REJECT --reject-with host-prohib
#### Now we need make sure the port knocking sequence is continuous.
#### If some random port is accessed during the knocking process, then the recent table will be cleared,
#### so that one has to do the knocking all over again.
$IPTABLES -A port-knocking -p tcp -m recent --remove
#$IPTABLES -A port-knocking -p tcp -m recent --remove -j REJECT
#### chain knocking-okey is to accept connection and clear the record
$IPTABLES -A knocking-okey -m state --state NEW -p tcp -m recent --remove -j ACCEPT
#### chain knocking-oops is to drop or reject connection and clear the record
$IPTABLES -A knocking-oops -m state --state NEW -p tcp -m recent --remove
#$IPTABLES -A knocking-oops -m state --state NEW -p tcp -m recent --remove -j REJECT
#### ======= port-knocking handling bulk end =============
#
#### Now the real part
$IPTABLES -A INPUT -i eth0 -p icmp -j icmp-packets
$IPTABLES -A INPUT -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPTABLES -A INPUT -i eth0 -p tcp --syn -j syn-flood
#$IPTABLES -A INPUT -i eth0 -p tcp --dport 22 -j ACCEPT
#### Instead of direct accept, ssh login attempts are first filtered with port knocking, thus the above line is commented out
$IPTABLES -A INPUT -i eth0 -p tcp --dport 22 -m state --state NEW -m recent --rcheck --seconds 20 --hitcount 3 -j knocking-okey
$IPTABLES -A INPUT -i eth0 -p tcp -j port-knocking
#
#### Now delete the blocking rules put in at the beginning
$IPTABLES -D INPUT 1
$IPTABLES -D FORWARD 1
$IPTABLES -D OUTPUT 1

In above script, the port-knocking chain makes sure the pre-defined ports are knocked, continusly and in a correct order; the knocking-okey chain accepts the packet and clears the recent table; the knocking-oops chain drops or rejects the packet and clears the recent table. It is more secure to drop a packet than to reject it, because a hacker could not see any result of the packet; but it takes more time to knock as you will usually wait for the time out error before sending out another packet (to knock another port).

Additional note: it is very easy to use the recent module of iptables to block too-frequent ssh longin requests, which is generally a malicious brute-force attack. There is an explanation and sample here.

No comments: