Written by Michael Shinn
|
Thursday, 17 April 2008 00:00 |
So we've been playing around with PortKnocking for some time, trying to find a good implementation that didn't create potential vulnerabilities itself - or at least presented as few as possible while working with some customers that needed something really fast. One interesting implementation that we've been toying with is written as a simple script. Whats also nice about this implementation is that it should be portable across Linux distributions, and should also work on almost anything else that support BASH scripts with some simple tweaks to the firewalling elements (changing iptables to ipf, etc.). |
|
Yep, you heard right, BASH scripts. What you got here is a 100% shell based portknocking server and client, with neither directly exposed to the traffic coming into the box its protecting so no need to worry about processing packets. This is a really handy feature, not being a service and not parsing packets directly, because that means we don't have to directly worry about our client and server handling them. Without further delay, here is the server pieces: #!/bin/bash # Change IP address to your own # Add this entry into /etc/syslog.conf # local7.* /var/log/boot.log
# touch /var/log/portknocking.log
#create portknock "hook" at top of firewall rules? # or have user create "hook" in their rulesets? # or give them a choice?
HOOK=NO LOG_REJECT=YES
if [ $HOOK = "YES" ]; then $IPTABLES -N PORTKNOCK #position 1 in the INPUT rules $IPTABLES -A INPUT -s 0.0.0.0/0 -d $our_ip -m state --state ESTABLISHED -p tcp --dport 22 -j ACCEPT 1 #need to put ESABLISHED rule first #log the hits? if [ $LOG_REJECT = "YES"; then $IPTABLES -A INPUT -s 0.0.0.0/0 -p tcp --dport 22 -j LOG --log-level notice --log-prefix "SSH REJECT " fi #and drop the baddies $IPTABLES -A INPUT -p tcp --dport 22 -j DROP 2 fi
# base port of knocking (range from port0 to port0+4095 must be free) port0=10000 # password pass="some_password" # unique string of knocking logs id_string="PORT_KNOCKING" # knocking log file log_file="/var/log/portknocking.log" # ip of our interface our_ip="xxx.xxx.xx.xxx"
# -------------------------------
# allowing only one connect from ip in this time period (seconds) # and also in other words max period of client and server clock desynchronisation time_period=100 # max time (in seconds) between two knocks in knocking sequence delta=2 # time period (in seconds) when door is open sleep_time=10
# don't touch time_flag=0 used_time=0 cnt=0
IPTABLES=/usr/sbin/iptables POLICY=`$IPTABLES -L INPUT | grep policy | awk '{print $4}' | tr -d \)`
# iptables initialisation for knocking listening port1=$(($port0+4095))
$IPTABLES -A INPUT -s 0.0.0.0/0 -d $our_ip -p tcp --syn --dport $port0:$port1 -j LOG --log-level notice --log-prefix "$id_string "
if [ $POLICY = "ACCEPT" ]; then $IPTABLES -A INPUT -p tcp --dport $port0:$port1 -j DROP fi
# allow only established ssh connection $IPTABLES -A INPUT -s 0.0.0.0/0 -d $our_ip -m state --state ESTABLISHED -p tcp --dport 22 -j ACCEPT
tail -n1 --follow=name --retry $log_file | { # read no using line read
# main cycle of reading log lines while [ 1 == 1 ] do read str
# get time in seconds since `00:00:00 1970-01-01 UTC' time=`date +%s`
# check is it our log line ok=`echo $str | grep $id_string` if [ -z "$ok" ]; then # to next iteration of main cycle continue fi
# extract source ip and destination port from log line for fld in $str do case "${fld:0:4}" in "SRC=") sip=${fld:4} ;; "DPT=") dport=${fld:4} esac done
# calculate secure combination of ports and time up to which this combinaton is valid if [ $time -ge $used_time ]; then time_stamp=$(($time/$time_period)) sum=`echo $pass$time_stamp | md5sum` i=0 sec_ports="" while [ $i -lt 16 ] do j=${sum:$i*2:2} port=$(($port0+0x$j*16+$i)) sec_ports="$sec_ports $port" i=$((i+1)) done
remainder=$(($time%$time_period)) used_time=$(($time-$remainder+$time_period)) used_ips=""
fi
# time period from last successsful processing dtime_flag=$(($time-$time_flag))
if [ $dtime_flag -gt $delta -o $cnt -eq 0 ]; then
# check if our ip already processed in current time period ok=`echo $used_ips | grep $sip` if [ "$ok" ]; then # to next iteration of main cycle continue fi
# begin processing for this ip cur_ip=$sip ports="" cnt=0
else # not allowed simultaneously process more then one ip if [ $sip != $cur_ip ]; then # to next iteration of main cycle continue fi fi
# time label of successful processing time_flag=$time # list of processing ports ports="$dport $ports" # port counter cnt=$((cnt+1))
# it's time to check port sequence if [ $cnt -eq 16 ]; then cnt=0
# check if incoming knocking correct for port in $sec_ports do ok=`echo $ports | grep $port` if [ -z "$ok" ]; then continue fi done
# open our door for some time if [ "$ok" ]; then used_ips="$used_ips $cur_ip"
# turn on incoming ssh connects
if [ $POLICY = "ACCEPT" ]; then $IPTABLES -D INPUT -p tcp --dport 22 -j DROP fi
$IPTABLES -A INPUT -s $cur_ip -d $our_ip -p tcp --syn --dport 22 -j ACCEPT
if [ $POLICY = "ACCEPT" ]; then $IPTABLES -A INPUT -p tcp --dport 22 -j DROP fi sleep $sleep_time # turn off incoming ssh connects $IPTABLES -D INPUT -s $cur_ip -d $our_ip -p tcp --syn --dport 22 -j ACCEPT fi fi
done }
And the client: #!/bin/bash
# program to knock (telnet or netcat) prog="telnet" # must be equal to time period on knocking server time_period=100 # period between knocking sequence and ssh connect sleep_period=2 # ssh user username=$1 # destination ip ip=$2
if [ $# -ne 2 ]; then echo "usage: ./clientname username ip_address" exit fi
read -p "enter base port of knocking: " -s port0 echo read -p "enter knocking password: " -s pass echo
# calculate secure sequence of ports time=`date +%s` time_stamp=$(($time/$time_period)) sum=`echo $pass$time_stamp | md5sum`
i=0 ports="" while [ $i -lt 16 ] do j=${sum:$i*2:2} port=$(($port0+0x$j*16+$i)) ports="$ports $port" i=$((i+1)) done
# start knocking ( for port in $ports do $prog $ip $port & done pkill $prog ) >/dev/null 2>&1 echo "knocking done" sleep $sleep_period
echo "trying to ssh ..." ssh -l $username $ip As the client is written as a script, we can use it on almost any OS, provided that we have sha1sum on the client and it can parse bash scripts. |