Le principe du "port knocking" est d'envoyer un ou plusieurs paquet particulier afin de permettre l'accès à un service. Cet article présente une forme scriptée du port knocking.





0x01. CONCEPT


Le Port knocking consiste en une séquence réseau permettant d'effectuer une action sur le serveur



1- On obtient les ports Knockknock au serveur sur le port 50000
2- On "frappe à la porte" une première fois, si le code n'est pas bon on est jeté
3- Si le 1er code est bon, on peut "frapper à la porte" une deuxième fois
4- Si le 2nd code est bon, on peut "frapper à la porte" une troisième fois
5- Si le 3ème code est bon, on peut envoyer la commande avec les 3 codes précédents sur le port de la 3 "porte"
6- La commande interne est exécutée

Dans le script que j'ai fait "frapper à la porte" consiste à envoyer un paquet avec cryptcat avec une clé définie dans le fichier Knock.keys.sh.

Les ports TCP utilisés sont obtenus une première fois sur le port 50000 et sont chiffré avec OpenSSL. Une fois déchiffrés, on peut "toquer à la porte" sur les 3 ports ainsi connus.

Ces ports TCP ne sont ouvert que temporairement.





0x02. CONFIGURATION


 
#!/bin/sh
 
 
###
### begin of conf
###
 
 
# Absolute path of executable
nc=/usr/bin/cryptcat
ncat=/usr/bin/ncat
iptables=/sbin/iptables
openssl=/usr/bin/openssl
wget=/usr/bin/wget
 
 
# adduser knockuser
# visudo
#  knockuser ALL=(ALL) NOPASSWD:/sbin/iptables
# configure a specific user allowed to run sudo iptables without password for this script
sudo=/usr/bin/sudo
 
# Log of the script
# touch /var/log/Knockd.sh.log
# chown knockuser /var/log/Knockd.sh.log
LOG="/var/log/knockd.log"
 
# Keys to encrypt open Knockd server port to upload to a webserver
KEY0='%KEY_FOR_ANNOUNCE%'
 
# Keys for cryptcat to decrypt received data from client
KEY1='%KEY_FOR_CRYPTCAT_1%'
KEY2='%KEY_FOR_CRYPTCAT_2%'
KEY3='%KEY_FOR_CRYPTCAT_3%'
 
# Passphrase to treat sent command. Command is defined by the orders of this passphrases.
# PASS1PASS2PASS3 => open port
# PASS3PASS1PASS2 => close port
# PASS2PASS2PASS3 => <do something>
PASS1='%PASS_FOR_COMMAND_1%'
PASS2='%PASS_FOR_COMMAND_2%'
PASS3='%PASS_FOR_COMMAND_3%'
 
# File on web server
PORTFILE=/var/www/_cle_public.html
 
# Write ports to a file
send_ports_to_file()
{
	echo '<!-- '$1' -->' > "$2"
}
 
# Port to open/close
SPORT=22
 
# Wait before waiting for a new client on port 50000 to get his IP address
WAIT=10
 
###
### end of conf
###
 




0x03. CLEFS


 
# Keys to de/encrypt open Knockd server port
KEY0='%KEY_FOR_ANNOUNCE%'
 
# Keys for cryptcat to decrypt received data from client
KEY1='%KEY_FOR_CRYPTCAT_1%'
KEY2='%KEY_FOR_CRYPTCAT_2%'
KEY3='%KEY_FOR_CRYPTCAT_3%'
 
# Passphrase to treat sent command.
PASS1='%PASS_FOR_COMMAND_1%'
PASS2='%PASS_FOR_COMMAND_2%'
PASS3='%PASS_FOR_COMMAND_3%'
 




0x04. SERVEUR


 
#!/bin/bash
 
 
 
#
# Configuration file
#
. ./Knockd.conf.sh
 
 
 
#
# Functions
#
cmd_up()
{
	IP=$1
	echo "[!] $$ | $(date) : SSH allowed to $IP" >> $LOG
	$iptables -D INPUT -p tcp -s $IP --dport $SPORT -j ACCEPT  >> $LOG 2>&1
	$iptables -I INPUT -p tcp -s $IP --dport $SPORT -j ACCEPT  >> $LOG 2>&1
}
 
cmd_down()
{
	IP=$1
	echo "[!] $$ | $(date) : SSH denied to $IP" >> $LOG
	$iptables -D INPUT -p tcp -s $IP --dport $SPORT -j DROP  >> $LOG 2>&1
	$iptables -I INPUT -p tcp -s $IP --dport $SPORT -j DROP  >> $LOG 2>&1
}
 
cmd_port()
{
	IP=$1
	DPORT=$2
	ACTION=$3
	# DROPED/ACCEPTED
	echo "[!] $$ | $(date) : $PORT $ACTION""ED to $IP" >> $LOG
	$iptables -D INPUT -p tcp -s $IP --dport $DPORT -j $ACTION  >> $LOG 2>&1
	$iptables -I INPUT -p tcp -s $IP --dport $DPORT -j $ACTION  >> $LOG 2>&1
}
 
_killit()
{
	# Log process killed
	echo "[x] $$ | $(date) : Killed : $1, $(basename $1)" >> $LOG
	killall -9 $(basename $1) >>$LOG 2>&1
}
 
clean()
{
	echo "[x] $$ | $(date) : Cleaning process" >> $LOG
	(
	 _killit $nc
	 _killit $ncat
  ) >> $LOG 2>&1
}
 
unload()
{
	# Kill myself
	printf "[-] $$ | Daemon stopping..."
	clean
	  for f in /var/run/knockd.* ; do
		kill -9 $(cat "$f") >/dev/null 2>&1
	done
	printf "\\b\\b\\b, done.\\n"
	exit
}
 
 
#
# Be sure there is no already launched Knockd daemon
#
echo "[-] Daemon starting with PID : $$"
echo "[-] Cleaning old process"
clean
rm -f /var/run/knockd.*
 
 
 
#
# void main() ;-)
#
if [ $# -eq 1 ] && [ "$1" = "stop" ]; then
	unload 0
	exit 0
fi
echo "[-] $$ | $(date) : Daemon starting with PID : $$" >> $LOG
echo $$ > /var/run/knockd.$$
 
while true; do
 
	# Random  62000 > port > 61000
	PORT1=6$(((RANDOM%1000)+1000))
	PORT2=6$(((RANDOM%1000)+1100))
	PORT3=6$(((RANDOM%1000)+1200))
 
	# Encrypt new ports with KEY0
	ports=$(echo $PORT1,$PORT2,$PORT3|$openssl enc -a -camellia-256-ecb -k $KEY0)
	send_ports_to_file $ports $PORTFILE
	echo "[-] $$ | $(date) : New ports : $PORT1, $PORT2, $PORT3" >> $LOG
	echo "[-] New ports : $PORT1, $PORT2, $PORT3"
 
	# Kill netcat before listening
	_killit $ncat
	sleep 1
	echo "[-] Waiting for connection ..."
	echo "[+] $$ | $(date) : Command : $ncat -4 -lp 50000 -vv" >> $LOG
	IP=$($ncat -4 -lp 50000 -vv 2>&1|grep 'Connection from'|awk '{ print $4 }'|head -n1|grep -oE "(([0-9]){1,3}\\.){3}([0-9]){1,3}")
	echo "[-] $$ | $(date) : Connection from '$IP'" >> $LOG
	echo "[-] '$IP' connected."
 
	if [ ! -z "$IP" ]; then 
		# Kill cryptcat before listening
		_killit $nc
 
		# Reset data
		DATA1="" ; DATA2="" ; DATA3="" ; CMD=""
 
		# Open port for 1st packet ...	
		cmd_port $IP $PORT1 ACCEPT
		DATA1=$($nc -w $WAIT -l -p $PORT1 -k "$KEY1" 2>>$LOG) ; echo "[-] Knock 1 received from $IP" >> $LOG
		echo "[-] Knock 1 received from $IP"
 
		if [ "$DATA1" = "$PASS1" ]; then
			# ... if OK then do the same fort 2nd packet ...
			cmd_port $IP $PORT2 ACCEPT
 			DATA2=$($nc -w $WAIT -l -p $PORT2 -k "$KEY2" 2>>$LOG) ; echo "[-] Knock 2 received from $IP" >> $LOG
			echo "[-] Knock 2 received from $IP"
 
			if [ "$DATA2" = "$PASS2" ]; then
				# ... and for the 3rd packet
				cmd_port $IP $PORT3 ACCEPT
 				DATA3=$($nc -w $WAIT -l -p $PORT3 -k "$KEY3" 2>>$LOG) ; echo "[-] Knock 3 received from $IP" >> $LOG
				echo "[-] Knock 3 received from $IP"
 
				if [ "$DATA3" = "$PASS3" ]; then
					CMD=$($nc -w $WAIT -l -p $PORT3 -k "$PASS1$PASS2$PASS3") ; echo "[-] Command received from $IP : $CMD" >> $LOG
					echo "[-] Command received from $IP : $CMD"
				fi
 
			fi
		fi
 
		cmd_port $IP $PORT1 DROP
		cmd_port $IP $PORT2 DROP
		cmd_port $IP $PORT3 DROP
 
		# Treat command
		echo "[-] Packet : $DATA1$DATA2$DATA3" >> "$LOG"
		OK=0
		if [ "$CMD" = "CMD=UP" ] ; then echo "[+] $IP make CMD=UP"          >> $LOG ; cmd_up $IP  ; OK=1 ; echo "[+] $IP make CMD=UP"   ; fi
		if [ "$CMD" = "CMD=DOWN" ] ; then echo "[+] $IP make CMD=DOWN"        >> $LOG ; cmd_down $IP; OK=1 ; echo "[+] $IP make CMD=DOWN" ; fi
 
		if [ $OK -eq 0 ] ; then clean ; fi
 
		# Wait before loops
		sleep $WAIT
		echo
		printf "\\n\\n" >> $LOG
 	fi
 
done
 




0x05. CLIENT


 
#!/bin/sh
 
error()
{
	echo "$*"
	exit 1
}
 
usage()
{
	echo "
 
Usage: $(basename $0) <host> <open|close>
 
 open        : configure firewall to open port
 close       : configure firewall to close port
 
"
	exit $1
}
 
 
 
#
# begin of knockc.conf
#
nc=/usr/bin/cryptcat
ncat=/usr/bin/ncat
ipextern=$HOME/.local/scr/ip-extern
openssl=/usr/bin/openssl
wget=/usr/bin/wget
 
# Keys
. Knock.keys.sh
 
# Wait for acknowledgement of cryptcat server part
WAIT=5
 
# Sleep between knocks
SLEEP=2
 
# Service port (SPORT) to open/close
SPORT=22
# end of knockc.conf
 
 
 
#
# Main
#
if [ $# -eq 2 ]; then
	HOST="$1"
	arg="$2"
else
	usage 0
fi
 
# Where to get ports
PORTPORTS="50000"
 
echo
echo '[-] Getting ports from : '$HOST:$PORTPORTS
ports=$(echo|$ncat $HOST $PORTPORTS|$openssl enc -d -a -camellia-256-ecb -k "$KEY0")
PORT1=$(echo $ports|cut -d',' -f1)
PORT2=$(echo $ports|cut -d',' -f2)
PORT3=$(echo $ports|cut -d',' -f3)
sleep $SLEEP
 
echo '[-] Knock opened ports : '$PORT1, $PORT2, $PORT3
printf "[+] Connecting to server..."
echo|$ncat -v $HOST $PORTPORTS >/dev/null 2>&1  && printf "\\b\\b\\b, done." || error 'Closed !'
 
 
 
printf "\\n[+] Knock ? "
echo $PASS1|$nc -v -w $WAIT -k "$KEY1" $HOST $PORT1 >/dev/null 2>&1 && echo 'Knock !' || error 'Closed !'
 
printf "[+] Knock ? "
echo $PASS2|$nc -v -w $WAIT -k "$KEY2" $HOST $PORT2 >/dev/null 2>&1 && echo 'Knock !' || error 'Closed !'
 
printf "[+] Knock ? "
echo $PASS3|$nc -v -w $WAIT -k "$KEY3" $HOST $PORT3 >/dev/null 2>&1 && echo 'Knock !' || error 'Closed !'
 
 
if [ "$arg" = "open" ]; then
 
	printf "[+] Sending command"
	echo CMD=UP|$nc -v -w $WAIT -k "$PASS1$PASS2$PASS3" $HOST $PORT3 >/dev/null 2>&1 && echo ', done.' || error 'Closed !'
	sleep $SLEEP
 
elif [ "$arg" = "close" ]; then
 
	printf "[+] Sending command"
	echo CMD=DOWN|$nc -v -w $WAIT -k "$PASS1$PASS2$PASS3" $HOST $PORT3 >/dev/null 2>&1 && echo ', done.' || error 'Closed !'
	sleep $SLEEP
 
fi
 
 
printf "[-] Port state : "
nmap --reason -Pn -sT -T5 "$HOST" -p $SPORT|egrep "open|filtered|closed"
echo
 
 



   =>   Écrit par : Nicolas, le 01 mars 2016


 
Mots clés :  
  network 
  
  security 
    >   Articles connexes :

GitVuln



HTTP Server, tell me who you are ?


Discuter avec un serveur web *apparement* muet ? Voici comment faire...

/tmp et /var/log en noexec sur macOS



Durcissement de Windows



6528158