--[ 11. Le saviez-vous ? La réponse ]------------------------------------ Une connexion ssh à un nouveau serveur fournit typiquement le résultat suivant : jb at zee:~/ > ssh -v 192.168.122.33 -l jb The authenticity of host '192.168.122.33 (192.168.122.33)' can't be established. RSA key fingerprint is 20:3a:ed:f1:5e:00:5c:12:d8:ef:b3:31:49:bb:17:52. Are you sure you want to continue connecting (yes/no)? Pour éviter ce message d'avertissement, il est nécessaire de distribuer au client les clés par un moyen sécurisé. Cette distribution peut être effectuée de façon statique, ce qui est peu commode lors de l'ajout de nouveau serveurs, ou via DNS, ce qu'établit la RFC 4255 [1] en définissant un nouveau type d'enregistrement DNS utilisé pour transporter ces clés : il s'agit de l'enregistrement SSHFP. Cette distribution permet de s'affranchir de la vérification manuelle de l'empreinte d'une clé publique SSH, et donc d'autoriser l'authentification auprès d'un serveur jusque-là inconnu par le client. Cette distribution des empreintes est également utile sur les systèmes sur lesquels l'utilisateur n'a pas de répertoire "home" dans lequel il pourrait enregistrer un fichier known_hosts (cas de certains bastions SSH durcis). Cette méthode de distribution des clés est-elle invulnérable aux attaques en interception réseau ? Une première attaque naïve consisterait à simplement intercepter la transaction DNS SSHFP pour fournir une fausse réponse SSHFP. Mais la RFC 4255, section 2.4, impose ("MUST") que la transaction DNS soit "sécurisée" soit au moyen de DNSSEC, soit au moyen d'un transport "sécurisé" (TSIG, SIG(0) ou IPsec). Autrement, le client SSH ne doit pas se reposer sur SSHFP pour autoriser l'authentification auprès d'un serveur SSH inconnu. Donc, l'utilisation de SSHFP semble invulnérable aux attaques en interception ? Pas sûr... Le client OpenSSH implémente la prise en compte des enregistrements SSHFP via l'option VerifyHostKeyDNS. Comment mettre cela en place : Côté serveur, l'utilitaire ssh-keygen permet de générer, à partir des clés publiques SSH contenues généralement dans /etc/ssh, les enregistrements DNS conformes à la syntaxe BIND : jb at server1:~# ssh-keygen -r server1 server1 IN SSHFP 1 1 282a7674ad8db5e59d5968abdecc12e8d04f1fa8 server1 IN SSHFP 2 1 94a8cdf4fbd9f79f8c780b33709085bacab83c70 Ces enregistrements ont le format suivant : 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | algorithm | fp type | / +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ / / / / fingerprint / / / +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ L'entier sur la quatrième colonne est le type d'algorithme à clé publique utilisé : Value Algorithm name ----- -------------- 0 reserved 1 RSA 2 DSS L'entier sur la cinquième colonne est le type de hash utilisé pour l'empreinte de la clé : Value Fingerprint type ----- ---------------- 0 reserved 1 SHA-1 La dernière entrée est bien sûr l'empreinte elle-même de la clé. Ces clés SSHFP doivent d'abord être transmises de façon sécurisée à l'administrateur du serveur DNS faisant autorité. L'ajout de ces clés dans ISC BIND se fait de façon semblable à toute nouvelle entrée DNS. Côté client, la résolution DNS sur SSHFP s'effectue sur le nom du serveur SSH passé en argument au client SSH. Pour cela, le client doit avoir positionné l'option "VerifyHostKeyDNS" à "yes" ou "ask". Cette configuration peut s'écrire dans le fichier .ssh/config par exemple ainsi : Host 192.168.122.* VerifyHostKeyDNS yes # (default: no) StrictHostKeyChecking ask # (default) De cette façon, chaque connexion à une machine ayant une adresse IP dans le sous-réseau 192.168.122.0/24 sera précédée d'une requête DNS SSHFP : jb at zee:~/ > ssh jb at server1.ssh.test.fr [...] debug1: found 2 insecure fingerprints in DNS debug1: matching host key fingerprint found in DNS [...] The authenticity of host 'server1.ssh.test.fr (192.168.122.33)' can't be established. RSA key fingerprint is 20:3a:ed:f1:5e:00:5c:12:d8:ef:b3:31:49:bb:17:52. Matching host key fingerprint found in DNS. Are you sure you want to continue connecting (yes/no)? L'utilisateur est bien notifié de l'existence d'une clé correspondante dans DNS, mais le comportement du client SSH n'a pas changé : l'utilisateur doit explicitement accepter de s'authentifier sur ce serveur pourtant référencé dans le DNS. Or nous ne souhaitons pas que l'utilisateur prenne la mauvaise habitude de taper "yes" lorsqu'une clé est inconnue, car on sait que les utilisateurs ne prennent pas le temps, ou ne peuvent pas, vérifier ces empreintes. C'est pourquoi nous privilégions le durcissement du client SSH : StrictHostKeyChecking yes De la sorte, la connexion à un serveur SSH présentant une empreinte inconnue ou modifiée est refusée. Les requêtes échangées entre le client et le serveur sont ici les suivantes : 0.001339 192.168.122.1 -> 192.168.122.32 DNS Standard query A server1.ssh.test.fr 0.001689 192.168.122.32 -> 192.168.122.1 DNS Standard query response A 192.168.122.33 0.094794 192.168.122.1 -> 192.168.122.32 DNS Standard query SSHFP server1.ssh.test.fr 0.095310 192.168.122.32 -> 192.168.122.1 DNS Standard query response SSHFP SSHFP En effet, la RFC 4255 (section 2.4) et l'implémentation d'OpenSSH exigent toutes deux qu'un mécanisme d'authentification de la réponse DNS SSHFP soit présent pour autoriser la connexion à un nouveau serveur SSH sans demander de confirmation auprès de l'utilisateur. Dans OpenSSH, cela se passe dans la fonction verify_host_key() de sshconnect.c et plus précisément dans la fonction getrrsetbyname() de getrrsetbyname.c : la réponse DNS reçue par le resolver doit posséder le drapeau "AD" (Authenticated Data) [2]. Si ce drapeau n'est pas positionné, alors OpenSSH se comporte comme si l'option VerifyHostKeyDNS était positionnée à "ask". Pour sécuriser la transaction DNS, nous travaillerons avec l'extension DNSSEC. TSIG est également possible mais les difficultés liées aux échanges de secrets partagés nous conduisent à préférer DNSSEC. Cependant, les resolvers actuels sur les postes clients (glibc notamment) ne sont pas capables de vérifier les signatures TSIG ni DNSSEC. Par conséquent, il est nécessaire, pour utiliser efficacement SSHFP, de mettre en oeuvre la vérification TSIG ou DNSSEC auprès de la fonction "resolver" du serveur cache accédé par le poste client (celui de /etc/resolv.conf). Nous allons voir que les nombreuses contraintes pour y parvenir exigent de maîtriser entièrement toute l'infrastructure y compris le poste client. Autrement, utiliser VerifyHostKeyDNS est plus dangereux que de ne pas l'utiliser. Il est d'abord nécessaire de préparer son resolver (glibc) à émettre une requête DNS formée de nature à recevoir une réponse authentifiée (c'est-à-dire avec le drapeau "AD" activé dans la réponse). Une solution logique serait de simplement activer ce drapeau dans la requête (et cela fonctionne auprès d'un cache BIND), à l'instar de la commande suivante : dig +adflag server.example.com @resolver La réponse reçue n'est pas signée, mais elle porte le drapeau AD si le resolver a pu valider la signature DNSSEC. Cependant ceci n'est pas possible aussi simplement avec la glibc. Un contournement pour la glibc consiste à activer l'option "edns0" de la libresolv (glibc), ce qui a également pour effet secondaire d'activer le bit DO dans l'extension EDNS0, et donc de récupérer les extensions DNSSEC en entier dans la réponse (mais qui sont totalement inutiles puisque non traitées par glibc...) mais avec le drapeau "AD" activé. Ceci se fait dans /etc/resolv.conf (attention aux démons DHCP ou network-manager qui écrasent ce fichier) : options edns0 Cependant cela ne fonctionne qu'à plusieurs conditions concernant la fonction "resolver" du serveur cache indiqué dans /etc/resolv.conf : - le resolver doit valider concrètement les réponses signées par DNSSEC qu'il reçoit ; - l'enregistrement SSHFP doit être signé DNSSEC, issu d'une zone ayant reçue une délégation signée par DNSSEC, etc, jusqu'à la zone racine ; - ou, à défaut de signature valide de la délégation de zone, notre resolver doit être configuré avec la clé publique de la bi-clé ayant servi à signer la zone comportant l'enregistrement SSHFP (mot-clé trusted-keys de BIND). Cette option est donc nécessaire pour les zones signées sur un DNS privé (qui ne sont pas dans le DNS accessible via la hiérarchie publique). Dans ce cas, tout fonctionne : la glibc reçoit une réponse authentifiée (drapeau AD), le client SSH autorise la connexion : debug1: Server host key: RSA e0:bc:a3:9a:dc:bc:73:8f:91:f4:fb:cd:c3:93:c9:bf debug1: found 2 secure fingerprints in DNS debug1: matching host key fingerprint found in DNS La connexion est autorisée même s'il y a eu un changement d'empreinte entre temps et que le fichier known_hosts contient une mauvaise empreinte. Notons que lors de l'autorisation d'une connexion par SSHFP, le fichier known_hosts n'est pas mis à jour. Notons également que le drapeau AD n'est pas présent si le serveur indiqué dans resolv.conf est autoritaire pour l'enregistrement SSHFP, puisqu'il n'y a alors personne qui vérifie les signatures DNSSEC. Sauf que... même avec toutes ces protections, et dès lors que l'option VerifyHostKeyDNS est activée, il suffit à un attaquant, positionné sur le réseau du poste client ou sur le réseau du serveur cache, d'intercepter le trafic réseau afin de : - envoyer une fausse réponse DNS lors de la recherche de l'adresse IP du serveur SSH sollicité (attaque classique) ; - envoyer une fausse réponse DNS lors de la recherche de l'empreinte (SSHFP) du serveur SSH, tout en activant le drapeau "AD", et peu importe que l'enregistrement soit signé puisque la signature est ignorée par la glibc ; - et ainsi détourner le client SSH vers un faux serveur SSH dont la clé publique sera approuvée sans aucun "warning" par le client SSH. Même avec StrictHostKeyChecking=yes. Même avec DNSSEC validé par le DNS cache. Même avec une ancienne empreinte présente dans le fichier known_hosts. Cette attaque n'aurait pas eu lieu sans l'utilisation de VerifyHostKeyDNS. En résumé, même avec DNSSEC, le lien entre le poste client et son DNS cache n'est pas sécurisé. Par conséquent, une compromission de ce lien, ou du serveur DNS cache lui-même, permet de remplacer l'adresse IP du serveur SSH demandé par l'adresse IP d'un serveur "pirate", et également de fournir au client SSH l'empreinte SSHFP du serveur "pirate". Si VerifyHostKeyDNS est activé, le client SSH se connecte en toute confiance au serveur "pirate" sans qu'aucun avertissement ne soit communiqué à l'utilisateur. Le palliatif aujourd'hui est d'utiliser DNSSEC ou TSIG jusqu'au poste client, c'est-à-dire d'utiliser sur localhost un resolver capable de valider DNSSEC ou TSIG, ce que la glibc ne fait pas. Les deux solutions sont proposées dans l'excellent article de G. Valadon et Y.-A. Perez présenté au SSTIC 2011 [3] : il s'agit, brièvement, soit d'utiliser le resolver lwres (basé sur ISC BIND) à la place de glibc (donc en modifiant nsswitch.conf), soit de placer "nameserver 127.0.0.1" dans /etc/resolv.conf et de configurer un BIND sur le poste client. Il y a alors peu de chance qu'un attaquant intercepte le trafic sur 127.0.0.1 :) Toutefois ces solutions restent difficiles à mettre en oeuvre sans une maîtrise entière des postes clients, et nous ne pouvons que nous associer à MM. Valadon et Perez dans le constat que les technologies actuelles ne sont malheureusement pas encore capables de protéger le dernier lien, entre le DNS cache et le poste client. -- Jean-Baptiste Aviat, Raphael Marichez et l'équipe HSC [1] http://www.ietf.org/rfc/rfc4255.txt [2] http://www.networksorcery.com/enp/protocol/dns.htm#AD [3] http://www.sstic.org/media/SSTIC2011/SSTIC-actes/architecture_dns_scurise/SSTIC2011-Article-architecture_dns_scurise-valadon_perez.pdf