Port Knocking avec Netfilter et Iptables

Jean Baptiste FAVRE

juillet 2009

Introduction

Il existe plusieurs façons de faire du port knocking, plus ou moins élaborées. On peut se contenter d'une séquence de port, ajouter un contenu aux paquets, chiffré / signé (ou pas), etc...
Les possibilités ne sont limitées que par votre imagination... et la puissance de la machine que vous protégez.
Car le problème est aussi là: plus vous choisirez une solution complexe, plus vous avez de chance d'être obligé d'ajouter un service sur votre machine. Et ceci n'est pas anodin car cela induit de nouvelle potentielles failles de sécurité.

Dans mon cas, j'ai pris le parti d'une solution la plus légère possible. Sa mise en œuvre se limite à l'utilisation d'iptables. Difficile de faire mieux, sauf à ne rien faire.

Principe

Le but du jeu consiste à n'ouvrir un port (exemple, le port 22) qu'aux IP qui auront composés avec succès un "code".

Le code correspond à une série de paquets TCP ou UDP, envoyés de façon séquentielle sur des ports déterminés.

Cette suite de port/protocole forme le code d'accès qui déverrouillera le port 22 pour une durée déterminée et pour l'IP d'origine seulement.

Il faut donc pouvoir:

Plus de précisions sur Wikipédia.

Configuration

Pour cela, nous allons utiliser le module ipt_recent fourni avec iptables.

Le principe: soient 4 port 1000/TCP, 2000/TCP, 3000/TCP et 4000/TCP

Dump des règles iptables

    :INPUT DROP [0:0]
    :FORWARD ACCEPT [0:0]
    :OUTPUT ACCEPT [0:0]
    :pk - [0:0]
    :pk1 - [0:0]
    :pk2 - [0:0]
    :pk3 - [0:0]
    -A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -m recent --rcheck --seconds 3 --name pk3 --rsource -j pk3
    -A INPUT -p tcp -m state --state NEW -m tcp --dport 1000 -m recent --set --name pk --rsource --seconds 3 -j DROP
    -A INPUT -p tcp -m state --state NEW -m tcp --dport 2000 -m recent --rcheck --seconds 3 --name pk --rsource -j pk
    -A INPUT -p tcp -m state --state NEW -m tcp --dport 3000 -m recent --rcheck --seconds 3 --name pk1 --rsource -j pk1
    -A INPUT -p tcp -m state --state NEW -m tcp --dport 4000 -m recent --rcheck --seconds 3 --name pk2 --rsource -j pk2
    -A pk -m recent --remove --name pk --rsource
    -A pk -m recent --set --name pk1 --rsource -j DROP
    -A pk1 -m recent --remove --name pk1 --rsource
    -A pk1 -m recent --set --name pk2 --rsource -j DROP
    -A pk2 -m recent --remove --name pk2 --rsource
    -A pk2 -m recent --set --name pk3 --rsource -j DROP
    -A pk3 -m recent --remove --name pk3 --rsource -j ACCEPT
    

Que ce passe-t-il dans cette série de règles iptables ?

  1. Un paquet à destination du port 1000/TCP sera enregistré (--rsource) et l'enregistrement marqué pk (--set --name pk).
  2. Un paquet provenant de la même IP (--rsource) à destination du port 2000 et marqué pk (--rcheck --name pk) est envoyé dans la chaîne pk.
    Là, il sera supprimé de la liste pk (--remove --name pk) et marqué pk1 (--set --name pk1). Le paquet est ensuite supprimé.
  3. Un paquet provenant de la même IP (--rsource) à destination du port 3000 et marqué pk1 (--rcheck --name pk1) est envoyé dans la chaîne pk1.
    Là, il sera supprimé de la liste pk1 (--remove --name pk1), marqué pk2 (--set --name pk2) et finalement supprimé.
  4. Un paquet provenant de la même IP (--rsource) à destination du port 4000 et marqué pk2 (--rcheck --name pk2) est envoyé dans la chaîne pk3.
    Là, il sera supprimé de la liste pk2 (--remove --name pk2), marqué pk3 (--set --name pk3) et finalement supprimé.
  5. Enfin, un paquet marquée pk3 a 3 secondes pour se connecter sur le port 22. Au délà de ces 3 secondes, il faudra recommencer la séquence.

Toute cette séquence doit se dérouler dans une limite de 3 secondes.

Bref, une fois ce "code" mis en place, vous ne pourrez plus vous connecter. Du tout... sauf si vous avez le script client que voici.

Script port-knocking client
    #!/bin/sh

    ####################################
    #                                  #
    # SCRIPT knock.sh                  #
    # permet de faire du port knocking #
    # necessite l'outil nc (netcat)   #
    #                                  #
    ####################################

    #Sortie erreur vers dev/null (pour telnet)
    exec 2>/dev/null

    NC=`which nc`
    TELNET=`which telnet`
    SSH=`which ssh`

    ResetColours="$(tput sgr0)"
    Green="$(tput setaf 2)"
    Red="$(tput setaf 1)"

    if [ -z $SSH ];then
        echo -e "$Red ERROR 12 $0 : \nBouhhhh ssh client side is not installed on your computer. \n Install it before restarting $0. \n Exiting!!!$ResetColours"
        exit 1
    fi

    #Vérification du nombre de paramètre 1=juste dest 2= option+dest
    if [ $# -eq 1 ]; then
        DEST=$1
    else
        DEST=$2
        OPT=$1
    fi

    case $OPT in
     "-v")
        SSH="$SSH -v"
        ;;
        *)
        SSH="$SSH"
        ;;
    esac

    if [ -z "$NC" ] && [ -z "$TELNET"];then
        echo -e "$Red ERROR 14 $0 : \n Netcat (nc) and Telnet are not installed on your computer. \n Install one of them before restarting $0. \n Exiting ...$ResetColours"
        exit 1
    fi

    if [ -n "$NC" ]; then
      echo "using nc to knock"
    SOFT=$NC
    else
        echo "using telnet to knock"
        SOFT=$TELNET;
    fi

    case $DEST in
        "server1")
        PORT_LIST="1000 2000 3000 4000"
        SERV="server.example.com"
        USER="user"
        ;;
        "server2")
        PORT_LIST="4000 3000 2000 1000"
        SERV="server2.example.com"
        USER="user2"
        ;;
        *)
        echo "usage $0 [option] {server1|server2}"
        echo " [option] -v: ssh verbose "
        exit 1
        ;;
    esac
    I=0
    for port in $PORT_LIST

      do
      case $SOFT in
          $NC)
          if [ $I -eq 0 ]; then
              echo -en "$Red.........\b\b\b\b\b\b\b\b\b$ResetColours"
          I=1
          fi

          $NC -w1 $SERV $port 2>/dev/null
          echo -en "$Green+++$ResetColours"
          ;;
                $TELNET)
          if [ $I -eq 0 ]; then
              echo -en ".........\b\b\b\b\b\b\b\b\b"
              I=1
          fi
          $TELNET $SERV $port >/dev/null &
          process=$!
          sleep 1
          kill -9 $process
          echo -en "+++"
          ;;
          *)
          echo "Comment est tu arrivé la ?"
          exit 1
          ;;
      esac
    done
    sleep 0.5
    exec 2>&1
    echo -n " "
    echo "$Green KNOCK KNOCK KNOCK !!!User $USER are you sure ? Type ssh pass !$ResetColours"
    $SSH $USER@$SERV

Et voilà. Maintenant, vous pourrez dormir tranquilles. Votre serveur SSH est bien à l'abri.

Format

Ce document est disponible aux formats suivants:

À propos de Jean Baptiste FAVRE

Je suis responsable d'exploitation dans le domaine de l'hébergement. Je travaille, entre autres, sur la virtualisation et l'amélioration des performances web. De temps en temps, j'arrive à décrocher de mon clavier pour lire un bon bouquin en écoutant de la musique.

License

Creative Commons License Cette publication est publiée sous contrat Creative Common by-nc-sa

Valid XHTML 1.0 Strict |  Valid CSS |  contrat Creative Common by-nc-sa

Table des matières

  1. Introduction
  2. Principe
  3. Configuration
  4. Format
  5. À propos ...
  6. License