04/10/2018 : Mise à jour en ajoutant les options ClientAliveCountMax et ClientAliveInterval au fichier sshd_config (explication dans l'article ^^)

23/09/2018: Ceci est une reprise du post original (lien non maintenu, la flemme, toussa...), mis à jour au grès des évolutions du protocol. Version actuelle basée sur une Debian GNU/Linux 9.5

05/03/2018: Mise à jour du fichier ssh_config (suppression des options RhostsRSAAuthentication et RSAAuthentication) suite à un échange avec Guy de Mölkky sur Mastodon

Pourquoi diable vouloir en ajouter ?

Depuis ma découverte du protocole Secure Shell (SSH), j'ai toujours cru qu'il s'agissait d'une solution miracle que rien ne pouvait ébranler.

De ce fait, lorsque j'installais un serveur, je laissais purement et simplement la configuration d'origine en place. Après tout, ça juste fonctionne... What else !

Mais ça c'était avant qu'un olibrius de Caroline du Nord vienne mettre les pieds dans le plat et envoie paître toutes mes certitudes.

En effet, une certaine organisation serait – dans certains cas – à même de déchiffrer le protocole Secure Shell (SSH) et/ou de s’emparer des clefs de session. Bien triste nouvelle si il en est !

Je me voyais déjà en position fœtale sous mon bureau lorsque j’entraperçus une lueur d'espoir dans les tuyaux de l'interweb !

Le but principal étant de modifier la configuration de base qui ressemble à ce joli patchwork :

Trois conventions pour les gouverner tous

En effet, à l'instar de SSL/TLS, il y a une démarche à suivre afin d'apporter un tantinet soit peu de sécurité à l'ensemble de l'édifice et de bien faire chier bisquer nos chères agences de renseignements...

En parcourant le web, on en ressort trois grands principes :

  • 1 : Ne pas utiliser les algorithmes pondus par le NIST qui sont volontairement affaiblis

  • 2 : Éliminer les algorithmes tout cassés tels que MD5 et SHA1

  • 3 :Privilégier les courbes elliptiques en lieu et place de l’algorithme RSA

Partant de ce constat regardons quelles sont les options qui nous sont offertes.

N.B. : La configuration qui va suivre part du principe que l'on assure la sécurité – du moins autant que faire se peut – côté serveur, peut-être au détriment de certaines compatibilités, tel que l'impossibilité d'utiliser certains clients sur certains systèmes

Un Serveur, des clients et des algorithmes

Le protocole SSH utilise un modèle client-serveur pour authentifier les deux parties et chiffrer les données entre elles.

Le serveur écoute sur un port désigné pour les connexions. Il est responsable de négocier la connexion sécurisée, d'authentifier la partie connexion et de générer l'environnement correct si les informations d'identification sont acceptées.

Le client, lui, est responsable de lancer le protocole TCP avec le serveur, de négocier la connexion sécurisée, de vérifier que l'identité du serveur correspond aux informations précédemment enregistrées et de fournir des informations d'identification pour s'authentifier.

Une session SSH s’opère donc en deux étapes distinctes :

  • La première est de convenir et d'établir le chiffrement pour protéger les communications futures.
  • La seconde consiste à authentifier l'utilisateur et à déterminer si l'accès au serveur doit être accordé (ou pas).

Donne-moi ta clef... Et prend la mienne

L’échange de clefs permettant d'établir un secret partagé au sein d'un canal non sécurisé se fait via l'algorithme de Diffie-Hellman, soit de sa variante basé sur les courbes elliptiques.

Mais c'est la seconde version qui va nous intéresser, car, souvenez-vous, nous devons privilégier les courbes elliptiques.
De plus, et même si il y a une solution, Diffie-Hellman est sujet à des attaques de type homme du milieu

Les exemples suivants sont basés sur une distribution Debian Gnu/Linux 9.5 (Stretch) avec une version 1:7.4p1-10+deb9u4 du paquet OpenSSH, qui est probablement la mise en œuvre du protocole SSH la plus déployé.

Cette dernière nous propose plusieurs algorithmes destinés à un échange de clefs :

[arnaud:~] $ ssh -Q kex
diffie-hellman-group1-sha1
diffie-hellman-group14-sha1
diffie-hellman-group14-sha256
diffie-hellman-group16-sha512
diffie-hellman-group18-sha512
diffie-hellman-group-exchange-sha1
diffie-hellman-group-exchange-sha256
ecdh-sha2-nistp256
ecdh-sha2-nistp384
ecdh-sha2-nistp521
curve25519-sha256
curve25519-sha256@libssh.org

Du coup, en se basant sur les principes de base évoqués plus tôt, c'est à dire en éliminant tout ce qui est en rapport avec le NIST ainsi que tout ce qui est faiblard et en privilégiant les courbes elliptiques ont va tabler sur :

curve25519-sha256
curve25519-sha256@libssh.org

Il vous reste à faire un choix entre les deux..

Côté serveur, il faudra donc renseigner les paramètres suivants :

Par exemple, dans le fichier /etc/ssh/ssh_config

KexAlgorithms curve25519-sha256 

Puis, dans le fichier /etc/ssh/sshd_config

KexAlgorithms curve25519-sha256

Allo, allo, monsieur le serveur !? Dites-moi...

Bon c'est bien beau tout ça, mais on aimerait partager nos clefs avec des entités de confiance et non pas avec le premier quidam du coin...

Pour mener à bien cette phase, le serveur signe la clef produite lors de l'échange précédent afin de prouver qu'il s'agisse bien de lui, c'est ce que l'on nomme l'authentification.

Comme pour l’échange de clefs, nous avons le choix entre plusieurs algorithmes :

[arnaud:~] $ ssh -Q key
ssh-ed25519
ssh-ed25519-cert-v01@openssh.com
ssh-rsa
ssh-dss
ecdsa-sha2-nistp256
ecdsa-sha2-nistp384
ecdsa-sha2-nistp521
ssh-rsa-cert-v01@openssh.com
ssh-dss-cert-v01@openssh.com
ecdsa-sha2-nistp256-cert-v01@openssh.com
ecdsa-sha2-nistp384-cert-v01@openssh.com
ecdsa-sha2-nistp521-cert-v01@openssh.com

Rebelote, on vire tous ce qu'il faut, et encore une fois les heureux gagnants sont :

ssh-ed25519
ssh-ed25519-cert-v01@openssh.com

Toujours sur le serveur, on modifie le fichier /etc/ssh/sshd_config :

Protocol 2
KexAlgorithms curve25519-sha256
HostKey /etc/ssh/ssh_host_ed25519_key

On supprime aussi les clefs générées lors de l'installation de OpenSSH, puis en générer une nouvelle de type ed25519

Suppression des clefs existantes :

[root:/etc/ssh] rm ssh_host_*key*

Génération d'une nouvelle clef :

 [root:/etc/ssh] ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" < /dev/null

Car ça... C'est vraiment toi !

De son côté, notre serveur aimerais aussi être sûr que ce soit bien le bon client qui frappe à la porte et non pas un indésirable... Pour mener à bien cette tâche, il y a différentes options disponibles...

L'authentification par mot de passe est l'une d’entre elles. On oublie d'entrée de jeu ! Vulnérabilité considérable aux attaques par force brute, de plus si un serveur est compromis, il y a une possibilité de dérober les mots de passe...

De la même façon dont le serveur s'authentifie, nous allons utiliser une clef publique qui est beaucoup plus sûre.

Les modifications, toujours sur le serveur, pour /etc/ssh/sshd_config :

PasswordAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes

Et pour /etc/ssh/ssh_config :

Host *
     PasswordAuthentication no
     ChallengeResponseAuthentication no
     PubkeyAuthentication yes

Sur notre client, il nous faut donc créer la paire de clef publique/privée afin de mener à bien cette authentification.

[arnaud:~] ssh-keygen -t ed25519 -o -a 1000

Appel aux options -o de la commande ssh-keygen afin de spécifier le nombre de tours -a de dérivation de la clef à appliquer. Un nombre élevé entraîne une vérification de la phrase de passe plus lente et une résistance accrue aux attaques par force brute si les clés venaient à être dérobées.

On prendra soin de renseigner une phrase de passe correcte – qui doit correspondre au minimum à un mot de passe aléatoire d’au moins 21 caractères, soit à une phrase de passe aléatoire d’au moins 12 mots – pour protéger cette clef puis de l'envoyer vers le serveur via la commande ssh-copy-id

[arnaud:~] ssh-copy-id -i /chemin/vers/clef.pub utilisateur@serveur

Tu portes des baskets, tu ne rentres pas...

Malgré l'authentification des clients via des clefs publiques, il est de bon ton de restreindre l'accès aux seuls utilisateurs désirés.

La création d'un groupe dédié est la manière la plus simple de parvenir à ses fins.

[root:~] # groupadd ssh-users
[root:~] # usermod -a -G ssh-users Nom_utlisateur

Puis rajouté la directive suivante dans le fichier /etc/ssh/sshd_config du serveur

AllowGroups ssh-users

Opérateur ! Ici Cipher...

Bon, nous sommes maintenant sur à 99% que le dialogue va bien avoir lieu entre les différents acteurs souhaités, ce qui est déjà une bonne chose.

Maintenant il faut encapsuler les données transmises dans un Gloubi-boulga de chiffrement...

[arnaud:~] ssh -Q cipher
3des-cbc
blowfish-cbc
cast128-cbc
arcfour
arcfour128
arcfour256
aes128-cbc
aes192-cbc
aes256-cbc
rijndael-cbc@lysator.liu.se
aes128-ctr
aes192-ctr
aes256-ctr
aes128-gcm@openssh.com
aes256-gcm@openssh.com
chacha20-poly1305@openssh.com

Là, le choix va être légèrement différent et les règles à appliquer vaguement
différentes.

En effet les bonnes pratiques tendent à utiliser un mode de chiffrement appelé
Authenticated encryption (AE) que l'on pourrait – sûrement mal – traduire par chiffrement assermentée. Ce mode fournit simultanément des garanties de confidentialité, d'intégrité et d'authenticité sur les données.

A cela il faut aussi rajouter la taille de la clef et des blocs utilisés : strict minimum 192 !

Ne parlons pas du fait que l'on évacue tout ce qui sent le camembert bien fait... Ce qui nous donne :

aes192-ctr
aes256-ctr
aes256-gcm@openssh.com
chacha20-poly1305@openssh.com

Le choix n'est pas fini car il reste un loup ! Effectivement, il faut savoir que le protocole SSH ne chiffre pas la taille des messages envoyés lorsque l'on utilise le mode Galois/Counter Mode (GCM).

Pour des raisons de compatibilité il est possible de laisser le mode de chiffrement basé sur un compteur (CounTeR, CTR), ce n'est pas mon cas...

Verdict final pour /etc/ssh/sshd_config :

Ciphers chacha20-poly1305@openssh.com

Et même punition pour /etc/ssh/ssh_config

Host *
    [...]
    Ciphers chacha20-poly1305@openssh.com

Message authentifié

Afin de fournir l’intégrité du message – sauf si un mode AE a été choisi précédemment – il faut attacher un code d'authentification à chaque messages (message authentication code MAC).

Il y a plusieurs méthodes disponibles, mais une seule doit être mis en application : Encrypt-then-MAC (EtM)

De nouveau un large choix est proposé :

[arnaud:~] ssh -Q mac
hmac-sha1
hmac-sha1-96
hmac-sha2-256
hmac-sha2-512
hmac-md5
hmac-md5-96
hmac-ripemd160
hmac-ripemd160@openssh.com
umac-64@openssh.com
umac-128@openssh.com
hmac-sha1-etm@openssh.com
hmac-sha1-96-etm@openssh.com
hmac-sha2-256-etm@openssh.com
hmac-sha2-512-etm@openssh.com
hmac-md5-etm@openssh.com
hmac-md5-96-etm@openssh.com
hmac-ripemd160-etm@openssh.com
umac-64-etm@openssh.com
umac-128-etm@openssh.com

Comme d'habitude, on retire tout ce qui est moisi, qui n'utilise pas EtM et dont la taille de la clef et du tag est inférieur à 128 bits.

La sentence pour /etc/ssh/sshd_config :

[...]
MACs umac-128-etm@openssh.com

Idem pour /etc/ssh/ssh_config :

Host *
    [...]
    MACs umac-128-etm@openssh.com

Faster ! Better ! Stronger !

OpenSSH regorge d'options et certaines peuvent présenter un danger.

C'est le cas pour UseRoaming ! En effet, en janvier 2016 une faille la concernant a été découverte.

Cette option permet au client de relancer une connexion SSH en cas de coupure. Pour fonctionner, cette fonctionnalité, doit être disponible sur le serveur or celle-ci n'a jamais été implémentée côté serveur.

On désactive donc l'option dans /etc/ssh/ssh_config :

Host *
    [...]
    UseRoaming no

This is this end...

Une fois la configuration mise en place, on obtient un résultat beaucoup plus acceptable via le formidable outil d'Aeris à savoir CryptCheck (L'ancienne version étant toujours disponible ici mais n'est plus à jour)

Après modifications

Une dernière pour la route.

Comme annoncé en début de publication, l'accent a été mis sur le fait d'essayer de déployer le maximum de sécurité côté serveur.

En revanche, il se peut que l'on soit amené à se connecter sur des serveurs ne nous appartenant pas et dont la configuration nous est totalement inconnue...

De ce fait, on peut assurer une compatibilité plus grande côté client en modifiant le fichier de configuration afin de partir du chiffremnt le plus haut et de le diminuer si le serveur ne l'accepte pas... L'option -v de la commande ssh est là pour nous indiquer qu'elle suite est utilisé.

Voici la configuration de mon fichier ~/.ssh/config :

KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs umac-128-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-ripemd160
HostKeyAlgorithms ssh-ed25519,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,ecdsa-sha2-nistp256,ssh-rsa
[...]

Il en reste, il faut finir !

Une option que j'affectionne particulièrement est la directive FROM que l'on peut utiliser dans le fichier ~/.ssh/authorized_keys
Elle permet de restreindre l'accès en fonction d'une IP unique, d'une plage, de plusieurs sources, etc.

Voici quelques exemples :

from="10.0.0.3" ssh-ed25519 AAZZcbRf....
from="1.2.3.0/24,44.55.66.77" ssh-ed25519 BBZZ+de_N....

Enfin, la modification du port de connexion par défaut 22 dans le fichier /etc/ssh/sshd_config du serveur permet de limiter les attaques de bases qui visent en priorité le sus-mentionné port et de ce fait alléger un peu les logs ^^

Autre option bien utile, si – comme moi – vous passez souvent à travers une liaison 4G instable, provoquant des déconnexions intempestives lors d’une inactivée plus ou moins longue.
Le réel problème n’étant pas la déconnexion en elle-même, mais le fait que cette dernière tourne toujours en tant que processus.

Exemple d’une déconnexion non voulue :

[root@Ahch-To] # packet_write_wait: Connection to 1.2.3.4 port 223344: Broken pipe

En se reconnectant on peut observer les connexions non closes :

Oui je sais...je ne suis pas balèze en retouche d'image...

Du coup afin d'éviter de tuer manuellement ces sessions inactives, nous allons utiliser les options ClientAliveCountMax et ClientAliveInterval dans le fichier sshd_config

Le manuel de sshd_config nous apprend respectivement que :

  • ClientAliveCountMax : Définis le nombres de messages qui peuvent être envoyés – par le biais du tunnel chiffré (contairement à l’option TCPKeepAlive) – sans que le démon sshd ne reçoive aucun message de retour de la part du client. La valeur par défaut de l’option étant fixée sur 3

  • ClientAliveInterval : Définis l’intervalle de temps entre chaque envoi de message de l’option ClientAliveCountMax. Par défaut la valeur est fixée sur 0, indiquant que ces messages ne seront pas envoyés au client.

Donc, si nous voulons que les sessions restées inactives, soit déconnectées au bout d’une minute, les valeurs à utiliser sont :

 ClientAliveCountMax 3
 ClientAliveInterval 20

Fichiers de configuration :

Les fichiers de configuration du serveur dans leurs intégralités :

/etc/ssh/ssh_config :

Host *
    KexAlgorithms curve25519-sha256
    PasswordAuthentication no
    ChallengeResponseAuthentication no
    PubkeyAuthentication yes
    HostKeyAlgorithms ssh-ed25519-cert-v01@openssh.com,ssh-ed25519
    Ciphers chacha20-poly1305@openssh.com
    MACs umac-128-etm@openssh.com
    UseRoaming no	
    SendEnv LANG LC_*
    HashKnownHosts yes
    GSSAPIAuthentication yes
    GSSAPIDelegateCredentials no

/etc/ssh/sshd_config :

Port 223344
Protocol 2
KexAlgorithms curve25519-sha256
HostKey /etc/ssh/ssh_host_ed25519_key
UsePrivilegeSeparation yes
Ciphers chacha20-poly1305@openssh.com
MACs umac-128-etm@openssh.com
AllowGroups ssh-users
SyslogFacility AUTH
LogLevel INFO
LoginGraceTime 120
PermitRootLogin no
StrictModes yes
PubkeyAuthentication yes
AuthorizedKeysFile	%h/.ssh/authorized_keys
IgnoreRhosts yes
PermitEmptyPasswords no
ChallengeResponseAuthentication no
PasswordAuthentication no
Subsystem sftp /usr/lib/openssh/sftp-server
UsePAM yes
UseDNS no
ClientAliveCountMax 3
ClientAliveInterval 20