Catégorie : Sécurité     Tags : ,      1 Commentaire

    Retrouvez cet article dans : Linux Magazine 88

    Une plate-forme d’échange non sécurisée est utilisable sur un réseau de confiance, entre des machines de confiance. Sur un réseau intranet, dans une certaine mesure, on peut imaginer que ces critères sont globalement réunis. Cependant, dès que les données échangées contiennent des informations nominatives ou des informations dont l’on doit garantir la confidentialité, le protocole FTP n’est pas la solution à privilégier, à plus forte raison sur un réseau public comme Internet.Il faut donc échanger des fichiers contenant des informations à ne pas diffuser entre deux machines. La mise en place d’un réseau privé virtuel basé sur IPSec est complexe et lourde. D’autre part, cette technologie est plutôt utilisée pour l’interconnexion de réseaux, mais pas pour la connexion de deux machines. L’utilisation d’un tunnel SSH peut nous aider grandement. SSH est un protocole ouvert qui permet d’échanger des données de manière sécurisée entre deux machines sur un réseau non fiable. Avec quelques précautions, il est possible de construire une machine bastion assez robuste pour remplir cette tâche. Cette machine permettra des connexions SSH, pour obtenir un shell et pouvoir effectuer des opérations de maintenance administratives ou seulement utiliser scp.

    /img-articles/lm/88/art-2/fig-1.jpg

    scp regroupe un certain nombre de commandes relativement "inoffensives" comme ls, mv, cp ou scp exécutables par un shell obtenu à travers une connexion SSH ou localement (mais c’est tout de même beaucoup moins intéressant). En supposant que l’on ne dispose que du shell scponly, il est possible avec un client SSH d’exécuter une commande comme :

    Outils pour la mise en œuvre

    Nous aurons besoin d’une distribution Linux minimaliste. Une Debian avec le strict nécessaire sera largement suffisante. deborphan et dpkg avec son option -l pourront être utiles pour déterminer les paquets inutiles installés ou les paquets orphelins.
    Il faudra tout de même quelques paquets supplémentaires :

    • scponly fournit un shell minimaliste qui ne permet que l’exécution des commandes scp. Il est compatible avec la plupart des clients SCP pour Microsoft Windows (WinSCP au moins). Il offre aussi une possibilité de mise en cage (chroot).
    • iptables regroupe les commandes qui permettent de manipuler Netfilter (le pare-feu du noyau Linux pour peu que le noyau ait été compilé avec l’option CONFIG_NETFILTER=Y).

    Il faudra configurer un démon SSH (OpenSSH est un excellent choix) de manière à n’autoriser que des utilisateurs connus, mais en ne leur laissant pas le loisir de manipuler leurs propres informations d’authentification. Le fichier de configuration /etc/ssh/sshd_config pourrait ressembler à ceci :

       Port 22
       Protocol 2
       HostKey /etc/ssh/ssh_host_rsa_key
       HostKey /etc/ssh/ssh_host_dsa_key
       UsePrivilegeSeparation yes
       KeyRegenerationInterval 3600
       ServerKeyBits 768
       SyslogFacility AUTH
       LogLevel INFO
       LoginGraceTime 600
       PermitRootLogin no
       StrictModes yes
       AllowUsers mon-compte-scponly
       RSAAuthentication yes
       PubkeyAuthentication yes
       AuthorizedKeysFile /etc/ssh/UserKeys/authorized_keys.%u
       IgnoreRhosts yes
       RhostsRSAAuthentication no
       HostbasedAuthentication no
       PermitEmptyPasswords no
       PasswordAuthentication no
       X11Forwarding no
       PrintMotd no
       PrintLastLog no
       KeepAlive yes
       UsePAM yes

    On n’y autorise que certains utilisateurs (AllowUsers + PermitRootLogin). Les mots de passe et les méthodes d’authentification faibles sont interdites (RSAAuthentication, PubkeyAuthentication, PermitEmptyPasswords, PasswordAuthentication, IgnoreRhosts, RhostsRSAAuthentication). Les clefs publiques pour la version 2 du protocole (Protocol) sont installées dans un emplacement spécifique hors des répertoires des utilisateurs (AuthorizedKeysFile).
    Pour une utilisation avec scponly de base, il suffit de créer le compte et de lui accorder comme shell /usr/bin/scponly. Ce shell doit apparaître dans /etc/shells.

    $ useradd -s /usr/bin/scponly mon-compte-scponly

    Pour une utilisation avec scponlyc (scponly chrooté), le compte doit être ajouté par le script setup_chroot.sh inclus dans la distribution de scponly. Il faut juste le décompresser, puis l’exécuter et répondre aux questions.

    $ cd /usr/share/doc/scponly/setup_chroot
    $ sudo gunzip setup_chroot.sh.gz
    $ sudo ./setup_chroot.sh

    Vous pouvez objecter, à juste titre, que modifier les fichiers de /usr/share n’est pas très propre. Il est préférable de décompresser ce fichier à un emplacement que vous maîtrisez ou même de l’exécuter après décompression grâce à gzip -cd ou son alias zcat.

    $ zcat /usr/share/doc/scponly/\
    setup_chroot/setup_chroot.sh.gz | \
    sudo sh

    Si vraiment la sécurité vous obnubile, gardez présent à l’esprit que scponlyc est setuid root... Déduisez-en les conséquences en cas de faille de sécurité ! Dans la même optique, la préparation de la cage copie un certain nombre de fichiers, dont en particulier libc6.so. Si une faille de sécurité est découverte dans la bibliothèque C de GNU, vous aurez tout intérêt à préparer une nouvelle cage. Le pare-feu est configuré de manière assez stricte pour n’autoriser que les connexions en SSH depuis certaines adresses IP. Tout le reste du trafic est ignoré (DROP) et les règles par défaut sont d’ignorer (default policy = DROP). S’il n’y a pas de serveur DNS accessible, il n’y a pas de résolution de noms. Si c’était le cas, il faudrait ajouter des règles supplémentaires comme :

       ${IPTABLES} -A OUTPUT -p tcp --dport 53 \
         --m state --state NEW -j ACCEPT
       ${IPTABLES} -A OUTPUT -p udp --dport 53 \
         --m state --state NEW -j ACCEPT

    ou de manière plus précise en spécifiant en plus les adresses des serveurs DNS. Il faut penser à ajouter l’IP d’une machine de rebond si nécessaire dans les règles du pare-feu. Dans tous les cas, aucun trafic n’est autorisé en sortie à l’exception du trafic relatif à des connexions déjà établies.
    Un script complet d’initialisation du pare-feu pourrait être le suivant. Il comprend les paramètres start, stop, passoire et status mais n’est vraiment pas destiné à remplacer votre script init (System V). En fait, il sera certainement beaucoup plus intéressant d’utiliser les commandes iptables-save(8) et iptables-restore(8).

      #!/bin/sh
       # Script d’initialisation d’un pare-feu local pour
       # une machine d’échange en DMZ
       # Ce script est fourni sans aucune garantie
       # Quelques remarques :
       # En fonction du trafic entrant, il peut
       # être souhaitable de ne pas enregistrer
       # les tentatives de connexions et donc,
       # de commenter les lignes contenant un
       # saut à la chaîne LOG (" -j LOG ")
       IP_LOCAL=W.X.Y.Z
       LISTE_IP_OK=ip.ok
       IPTABLES=/sbin/iptables
       MODPROBE=/sbin/modprobe
       DEFAULT_POLICY=DROP
       [ -x ${IPTABLES} ] || exit 2
       [ -x ${MODPROBE} ] || exit 3
       # Affichage des règles en cours
       function status() {
        ${IPTABLES} --line-numbers --list -v
       }
       # Définit une règle par défaut
       function default_policy() {
        case $1 in
         ACCEPT)
          POLICY=ACCEPT
         ;;
         DENY)
          POLICY=DENY
         ;;
         DROP)
          POLICY=DROP
         ;;
         *)
          POLICY=${DEFAULT_POLICY}
         ;;
        esac
        for CHAIN in {IN,OUT}PUT FORWARD
        do
         ${IPTABLES} -P ${CHAIN} ${POLICY}
        done
       }
       # Purge des règles.
       # En fonction des règles par défaut (DEFAULT_POLICY),
       # on peut scier la branche
       # sur laquelle on est assis ... :(
       function flush() {
        for ACTION in flush delete-chain
        do
         ${IPTABLES} --${ACTION}
        done
       }
       # Validation systématique du dialogue sur
       # la boucle locale
       # À vérifier son utilité ou sa pertinence
       # dans chaque contexte
       function local_ok() {
        ${IPTABLES} -A INPUT -i lo -j ACCEPT
        ${IPTABLES} -A OUTPUT -o lo -j ACCEPT
       }
       # Autorisation des connexions entrantes
       # en SSH pour une liste d’adresses IP
       # Cette fonction s’appuie sur le contenu
       # d’un fichier qui contient une liste
       # d’adresses IP (séparées par des
       # espaces ou des sauts à la ligne)
       function user_ok() {
        for SOURCE in `cat ${LISTE_IP_OK}`
        do
         ${IPTABLES} -A INPUT -s ${SOURCE} -p tcp \
           -m state --state NEW --dport 22 -j ACCEPT
         ${IPTABLES} -A INPUT -s ${SOURCE} -p tcp \
           -m state --state RELATED,ESTABLISHED -j ACCEPT
        done
       }
       # On jette tout le trafic martien, comme
       # des datagrammes dont l’adresse
       # de provenance est improbable ou impossible.
       # À corriger si vous avez justement des
       # adresses ou des plages d’adresses
       # valides dans ce qui suit.
       function anti_spoof() {
        for SOURCE in \
         255.0.0.0/8      0.0.0.0/8       127.0.0.0/8 \
         192.168.0.0/16   172.16.0.0/12   10.0.0.0/8  \
         ${IP_LOCAL}
        do
         ${IPTABLES} -A INPUT -s ${SOURCE} -j LOG --log-prefix "Spoof IP "
         ${IPTABLES} -A INPUT -s ${SOURCE} -j DROP
        done
       }
       # Filtrage et journalisation du trafic entrant
       # À rapprocher de la première remarque
       # concernant la journalisation
       function inbound_filter() {
        ${IPTABLES} -A INPUT -p tcp ! --syn -m state \
          --state NEW -j LOG --log-prefix "Stealth scan ? "
        ${IPTABLES} -A INPUT -p tcp ! --syn -m state \
          --state NEW -j DROP
        ${IPTABLES} -A INPUT -j LOG \
          --log-prefix "Dropped, finally (INPUT) "
        ${IPTABLES} -A INPUT -j DROP
       }
       # Filtrage et journalisation du trafic sortant
       # À rapprocher de la première remarque
       # concernant la journalisation
       function outbound_filter() {
        ${IPTABLES} -I OUTPUT 1 -m state --state RELATED,ESTABLISHED -j ACCEPT
        ${IPTABLES} -A OUTPUT -j LOG --log-prefix "Dropped, finally (OUTPUT) "
        ${IPTABLES} -A OUTPUT -j DROP
       }
       # Filtrage et journalisation du trafic retransmis
       # À rapprocher de la première remarque
       # concernant la journalisation
       function forward_filter() {
        ${IPTABLES} -A FORWARD -j LOG --log-prefix "Dropped, finally (FORWARD) "
        ${IPTABLES} -A FORWARD -j DROP
       }
       # Et c’est ici que l’on applique ce que l’on doit ...
       case $1
       in
        # Activation du filtrage : on purge les
        # règles en place. ON définit une règle
        # par défaut qui est de tout jeter,
        # puis on valide le trafic sur la boucle
        # locale, le trafic en provenance des
        # adresses listées explicitement, on
        # jette le trafic martien, on bloque
        # le reste du trafic entrant, sortant ou
        # routé en jetant les scans SYN-stealth
        # ou supposés en entrée
        start)
         ${MODPROBE} ip_tables
         flush
         default_policy DROP
         local_ok
         user_ok
         anti_spoof
         inbound_filter
         forward_filter
         outbound_filter
        ;;
        # On purge les règles mais on a une règle par
        # défaut qui est de jeter tout ce
        # qui entre ou qui sort.
        stop)
         flush
         default_policy DROP
        ;;
        # On affiche juste les règles en cours
        status)
         status
        ;;
        # Particulièrement cool pour un pare-feu :(
        # /!\ On accepte tout /!\
        passoire)
         flush
         default_policy ACCEPT
         status
        ;;
        # Un petit message d’info sur ce
        # qu’il est possible de faire
        *)
         echo “Usage : $0 <start|stop|passoire|status>”
         exit 1
        ;;
       esac
       exit 0

    Pour activer le filtrage et en supposant que vous êtes connecté sur une console virtuelle en local (pas par un pseudo-TTY), l’action start de ce script devrait verrouiller l’accès depuis l’extérieur à votre serveur au seul port TCP 22. Les adresses IP autorisées doivent être listées dans un fichier ip.ok.
    Ensuite, le script init iptables, fourni par Debian, fournit une interface très utilisable pour les sauvegardes de tables et de restaurations. Pour définir le jeu de règles activé par défaut, il vous suffira d’exécuter la commande suivante :

    $ /etc/init.d/iptables save active

    Et si l’on a besoin de réaliser une mise à jour de sécurité ?

    Pour la plupart des systèmes de mises à jour automatisées, cela implique une connectivité qui n’est pas toujours au rendez-vous (APT, urpmi, up2date peuvent accéder à des mises à jour depuis des serveurs HTTP ou HTTPS).
    Pour une Debian, il y aura besoin de récupérer des paquets depuis un serveur externe, ce qui est a priori contraire au principe d’empêcher les connexions initiées par notre serveur. Dans le pire des cas, il faudra récupérer des fichiers et les copier manuellement. Perspective peu encourageante. Oui, mais... avec OpenSSH, il est possible de créer un tunnel, et surtout dans le cas qui nous intéresse, un tunnel à l’envers. Grâce à SSH, il est possible de créer un tunnel qui va permettre d’accéder temporairement à des serveurs web externes.
    On réalise ce petit miracle en redirigeant le port TCP de la machine distante vers le port d’écoute du serveur web à partir d’une machine hors de la DMZ. "À l’envers" qualifie un tunnel qui autorise la connexion depuis la machine sur laquelle on se connecte. En pratique, voici deux exemples qui illustrent ce procédé très simplement pour Debian GNU/Linux (en fait, pour des mises à jour via APT) (voir figure 2).

    Exemple 1 : Le plus transparent

    Le fichier /etc/hosts de la machine en DMZ (dans notre cas ma_machine_bastion) contient la ligne :

    127.0.0.1 localhost.localdomain localhost mon_miroir_debian

    Le fichier /etc/apt/sources.list contient une déclaration tout à fait standard :

    /img-articles/lm/88/art-2/fig-2.jpg

     

    deb http://mon_miroir_debian/debian stable main contrib non-free
    deb http://mon_miroir_debian/debian-non-US stable/non-US main contrib non-free
    deb http://mon_miroir_debian/debian-security stable/updates main contrib non-free

    Pour mettre à jour la distribution, il suffit alors de :

     

    $ ssh -l root -R 80:mon_miroir_debian:80 ma_machine_bastion
    $ apt-get update && apt-get dist-upgrade
    $ apt-get clean

    La première instruction crée un tunnel du port TCP 80 sur ma_machine_bastion vers le port TCP 80 du serveur web contenant les miroirs. On spécifie un compte root pour la connexion parce que le port TCP d’écoute sur ma_machine_bastion est privilégié (les ports TCP et UDP < 1024 sont privilégiés sur les UNIX et Linux).

    La deuxième ligne met à jour les listes de paquets et procède à la mise à jour.
    La dernière ligne vide les paquets téléchargés qui n’ont plus de raison de rester dans le cache.

    Exemple 2 : Le plus général

    Si la connexion directe en root n’est pas autorisée, il vous est alors difficile (impossible est l’adjectif exact ;o) de créer le tunnel à l’écoute sur le port 80. Mais il est aussi possible de créer un tunnel depuis un port non privilégié. L’entrée dans le fichier /etc/hosts est toujours valable. Le fichier /etc/apt/sources.list contient une déclaration dans laquelle on précise un port TCP (8000) différent du port standard 80 qui est privilégié. Cette astuce permet de créer le tunnel même depuis un compte non privilégié :

    deb http://mon_miroir_debian:8000/debian stable main contrib non-free
    deb http://mon_miroir_debian:8000/debian-non-US stable/non-US main contrib non-free
    deb http://mon_miroir_debian:8000/debian-security stable/updates main contrib non-free

    Pour la mise à jour, il suffit de :

    $ ssh -R 8000:mon_miroir_debian:80 ma_machine_bastion
    $ sudo apt-get update && sudo apt-get dist-upgrade
    $ sudo apt-get clean

    La première ligne redirige le port 8000 de ma_machine_bastion vers le port 80 de mon_miroir_debian. On est alors connecté avec un compte non privilégié et c’est pour cette raison que l’on doit utiliser sudo (ou tout autre moyen pour élever ses privilèges) pour les commandes apt-get. Si le miroir de mises à jour est accessible en HTTPS, le principe reste le même. Néanmoins, il faudra se baser sur le port TCP 443. Pour un protocole comme FTP, plusieurs ports TCP sont utilisés. Le port 21 est utilisé pour les commandes, et les réponses pour les ports négociés sont utilisées sur le port TCP 20 (ftp-data). Ensuite, les données transitent sur des ports TCP supérieurs à 1024. Cette méthode ne s’applique donc pas, et il faudra se rabattre sur d’autres méthodes (proxy FTP, VPN, etc.).

    Mééééh, ça marche pas avec WinSCP...

    Que faire si vous utilisez un client graphique comme WinSCP et que vous utilisez la version chrootée ? Vous butez sur des messages grossiers concernant une fin de fichier ?
    Il y a une opération supplémentaire à réaliser qui consiste à compiler un fichier groups.c. Il faut pour cela un gcc, mais il est conseillé de le compiler sur une autre machine pour ne pas avoir à laisser de compilateur sur une machine bastion. Ça se fait simplement avec :

    gcc /usr/share/doc/scponly/groups.c -o groups

    Et, en copiant le fichier ainsi obtenu dans les répertoires bin ET usr/bin de la cage de l’utilisateur, vous évitez normalement ce problème. Le source C révèle uniquement des print ... rien de bien dangereux a priori.

    Conclusion

    Grâce à quelques Logiciels libres et en s’appuyant sur un protocole ouvert et sécurisé, il est possible de configurer une plate-forme d’échange de fichiers à l’aide d’une machine-bastion qui pourrait se trouver sur une DMZ ou directement connectée à internet.

    Réréférences
    La page de scponly http://www.sublimation.org/scponly/
    BAUER (Michael D.), Building secure servers with Linux, O’Reilly.

    Posté par (La rédaction) | Signature : Laurent Gautrot | Article paru dans Creative Commons License

    Il y a actuellement un commentaire dans “Une plate-forme d’échange sécurisée avec scponly”

    1. 1 Le 24 janvier 2008, lerouge[10] ecrivait:

      Quelques remarques.

      1/ Concernant la configuration du daemon SSH.

      Les directives
      – ‘KeyRegenerationInterval 3600′
      – ‘ServerKeyBits 768′
      – ‘RhostsRSAAuthentication no’
      sont spécifiques à l’utilisation de la version 1 du protocole (d’apres ‘man sshd_config’ sur Debian).
      Or il est spécifié ‘Protocol 2′.
      Pourquoi laisser ces directives ?

      2/ Les utilisateurs autorisés

      Il est écrit « On n’y autorise que certains utilisateurs (AllowUsers + PermitRootLogin) ».
      Or la directive ‘PermitRootLogin’ est bien fixée à ‘no’.
      Par conséquent, un login root (direct) est interdit.
      Il ne s’agit donc pas d’un utilisateur autorisé à ouvrir une session SSH.

      3/ Les méthodes d’authentication faibles

      Il est écrit « Les mots de passe et les méthodes d’authentification faibles sont interdites (RSAAuthentication, PubkeyAuthentication, PermitEmptyPasswords, PasswordAuthentication, IgnoreRhosts, RhostsRSAAuthentication) ».
      Or la directive ‘RSAAuthentication’ est fixée à ‘yes’.
      Elles est donc autorisée, et non interdite comme annoncé dans la phrase sus-citée.

      Ce qui n’enlève rien à l’intérêt de l’article ;)

      Mes 0,02 €

    Laissez une réponse

    Vous devez avoir ouvert une session pour écrire un commentaire.