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