29 juillet 2020 Astuces Vlad

TLS 1.3, ESNI, DoH, interception... it's not that complicated 😉

TLS 1.3

Bien que ce sujet ne soit finalement pas très nouveau, je profite des vacances pour parler d’un sujet souvent mal compris : TLS 1.3.

Avec NoLimitSecu, nous avions enregistré un épisode l'année dernière (https://www.nolimitsecu.fr/tls-1-3/), mais je trouve qu'il manque certains points que vous trouverez détaillés ci-dessous.
⚠️ Petite parenthèse : pensez à la dépréciation de TLS 1.0 et 1.1 par presque tous les acteurs du web entre l'été et l'automne 2020.

SSL/TLS

Le sujet a déjà été traité de nombreuses fois : ce sont les noms des protocoles de chiffrement les plus utilisés pour sécuriser les échanges web (HTTP).
SSL (Secure Sockets Layer) est l’ancienne version, tandis que TLS (Transport Layer Security) est la version plus récente.

Lorsqu’un client (navigateur) se connecte à un site web (par exemple : www.leroymerlin.fr ou www.bricoprive.com... oui, je fais des travaux 😉), si l’accès n’est pas déjà chiffré, une redirection est généralement faite vers la version chiffrée du site, car à terme, les navigateurs n’accepteront que les sites chiffrés.

Très simplement, SSL et TLS offrent deux fonctionnalités principales :

  1. Prouver l’identité d’un service (site web, service mail, API…) grâce à un mécanisme de certificat. Le serveur envoie au client un certificat signé par une autorité de certification de confiance, que le client peut vérifier ;

  2. Chiffrer les échanges grâce à différentes suites cryptographiques symétriques comme le standard américain AES (et des mécanismes d'échange de clés que nous ne détaillerons pas ici).

SSL comporte de nombreuses failles et n’est donc plus recommandé. Les premières versions de TLS sont également vulnérables à diverses attaques. Il est donc recommandé d’utiliser au minimum TLS 1.2.

Pour plus de détails, consultez « Fin de vie de TLS 1.0 et TLS 1.1 ».

Dans le passé, il n'était pas possible de proposer plusieurs services différents activés avec SSL sur une même adresse IP, du moins pas sur le même port. Par exemple, sur l'adresse IP 1.2.3.4 et le port 443 (port par défaut pour HTTPS), il n'était pas possible d'héberger à la fois un site web chiffré https://www.something.com et un VPN SSL https://distantaccess.something.com.

En effet, lors de la négociation du chiffrement SSL, le processus était le suivant :

  1. Résolution de l'adresse IP du serveur à partir de son nom de domaine (https://www.something.com se résolvant en l'adresse IP 1.2.3.4).

  2. Connexion à l'adresse IP du serveur (sur le port 443, mais ignorons ce détail).

  3. Réception en retour du certificat signé contenant, entre autres, un champ "Nom Commun / CN" avec le nom du serveur (https://www.something.com) pour prouver son identité.

Le problème était que le serveur n'avait aucun moyen de savoir quel nom le client essayait d'atteindre (https://www.something.com) et ne pouvait associer qu'un seul certificat, donc un seul nom de domaine, et par conséquent un seul service. (En réalité, c'est partiellement faux, car certains outils comme OpenVPN permettaient d'héberger plusieurs services, mais ce n'est pas le sujet, la détection se faisant après la négociation SSL/TLS).

Nous étions alors contraints de dédier une adresse IP par service (car il n'était pas possible d'utiliser un autre port que le 443).

Une solution acceptable consistait à utiliser un certificat avec un nom principal (https://www.something.com) et contenant également un champ "Subject Alternative Names / SAN". Dans ce champ, on ajoutait les noms des autres services (thing.thing.com, thing.thing.com, etc.). Cependant, cette solution ne permettait ni de s'adapter à grande échelle ni d'être dynamique (un certificat étant signé pour une longue période, devoir en générer un nouveau dès qu'on devait ajouter ou supprimer un service n'était pas pratique).

Le problème a été rapidement corrigé grâce à la fonctionnalité "Server Name Indication / SNI", une extension du protocole TLS datant de 2003 qui permet au client de spécifier dans sa requête le nom de domaine du service à atteindre. Pour les courageux, je recommande la lecture de la RFC 3546 : https://www.ietf.org/rfc/rfc3546.txt.

Une analogie pourrait être la suivante :

  • Avant, les facteurs avaient uniquement l'adresse et pas le nom. Il n'y avait qu'un destinataire possible par adresse postale.

  • Le SAN est comme un service de conciergerie ayant une liste de noms pour une adresse donnée et collectant le courrier pour les habitants.

  • Le SNI équivaut à ce que le facteur ait enfin le nom et que chaque habitant puisse gérer son propre nom sur sa boîte aux lettres.

Depuis, le champ SAN a simplement remplacé le champ CN, ce dernier n'étant plus pris en charge que par compatibilité rétroactive (source).

Voici un schéma tiré de CloudFlare pour illustrer cela :

De nos jours, cette fonctionnalité est largement utilisée, notamment par les fournisseurs de Cloud, les fournisseurs de SaaS et surtout les CDN (Content Delivery Network comme CloudFlare, Akamai, CD Network, CloudFront...), qui hébergent des centaines de services (sites) sur des bataillons de serveurs identiques (ou presque).

Encrypt-then-MAC, MAC-then-Encrypt, Je-comprends-riennnnnn...

De nos jours, les messages chiffrés sont souvent accompagnés de données permettant de garantir leur intégrité, c'est-à-dire d'assurer qu'ils n'ont pas été modifiés. Cela ne concerne pas l'authentification au sens utilisateur (mot de passe, authentification forte...), mais plutôt l'authentification du message en lui-même. En d'autres termes, cela prouve que le message n'a pas été altéré et que son intégrité reste intacte.

Généralement

(oui, deux fois en deux phrases, mais pourquoi pas 😆), cette authentification repose sur un condensat (ou "digest") généré à partir du message (chiffré ou non), de données techniques, et parfois de données aléatoires, pour éviter les attaques de type rejouement (on parle de "Nonce" en anglais ou parfois de "IV" pour "Initialization Vector").

Le terme courant pour ce mécanisme est MAC (Message Authentication Code).

Il existe plusieurs façons de mettre en œuvre ce processus d'authentification, détaillées ci-dessous :

  1. Ne rien faire 👍

  2. Générer le condensat du message en clair, puis chiffrer l'ensemble :
    MAC-then-Encrypt / Authenticate then Encrypt / MtE

  3. Générer le condensat du message en clair, chiffrer le message et envoyer les deux :
    Encrypt-and-MAC / Encrypt and Authenticate / E&M

  4. Chiffrer le message, générer son condensat et envoyer les deux :
    Encrypt-then-MAC / Encrypt then Authenticate / EtM

Les approches les plus courantes :

  • IPSec : utilise Encrypt-then-MAC

  • SSL (pas TLS) : utilise MAC-then-Encrypt

  • SSH : utilise Encrypt-and-MAC

1. Ne rien faire

Cette approche consiste littéralement à ne rien faire, et c'est la pire des solutions. Dans ce cas, les messages peuvent être modifiés ou rejoués sans aucun moyen de le détecter. Bref, c'est catastrophique 😜. Petite vidéo illustrative : https://www.youtube.com/watch?v=PuCRGsC9XhU

2. MAC-then-Encrypt

Prenons un message en clair nommé msg (ou Plaintext) et un nombre aléatoire random (ou Nonce).

Le processus de MAC-then-Encrypt consiste à envoyer :
Encrypt(msg concaténé avec Hash(random + msg))

Le schéma de Wikipedia peut prêter à confusion avec l'usage de Key1 et Key2, mais il aide à visualiser le concept :

(Insérez une image si nécessaire)

Cette méthode a été critiquée dans certains cas, car l'intégrité peut être compromise si le chiffrement n'est pas bien conçu. On préfère généralement des méthodes plus robustes comme Encrypt-then-MAC.

Souhaitez-vous approfondir chaque méthode ou explorer d'autres concepts liés à la sécurité des messages chiffrés ?

Cela garantit l'intégrité du message en clair, mais pas celle du message chiffré. Sans déchiffrement, il est impossible de savoir si le message a été modifié.

Ce mode est vulnérable à plusieurs attaques :

Encrypt-and-MAC

Encrypt-and-MAC revient à envoyer :
Encrypt(msg) concaténé avec Hash(random + msg)

L'intégrité du texte chiffré n'est pas assurée, ce qui permet de lancer des attaques en manipulant le texte chiffré. En revanche, l'intégrité du texte en clair est garantie, mais il est nécessaire de le déchiffrer, ce qui peut entraîner des erreurs et être exploité.

Enfin, selon l'implémentation, si les données "random" ne contiennent pas de compteur, il est possible de mener des attaques par texte clair connu sur le condensat.

Encrypt-then-MAC

Encrypt-then-MAC revient à envoyer :
Encrypt(msg) concaténé avec Hash(random + Encrypt(msg))

Ce mode permet de garantir l'intégrité du texte chiffré, mais si l'algorithme de condensation est compromis ou affaibli, il devient possible de réaliser des attaques de type "tampering" (modification).

Cela reste néanmoins le mode le plus robuste à ce jour.

TLS 1.3

La dernière version sécurisée de TLS étant la 1.2, sans entrer dans les détails, il serait facile de croire que TLS 1.3 n’est qu’une simple évolution, alors qu’en réalité, il s’agit d’une véritable rupture. Il aurait été préférable de l’appeler TLS 2.0, mais la dénomination est un sujet complexe (voir la vidéo à la fin, à 38:54).

Cette version apporte plusieurs changements notables :

  • Meilleure vitesse lors de la négociation, en réduisant les allers-retours entre le client et le serveur à un seul échange (contre deux auparavant), appelé "RTT" (Round Trip Time Resumption). Et où est le dernier R, vous demandez-vous ? Bonne question ! Si le client s'est déjà connecté au serveur, on est alors dans un cas d'optimisation appelé "0-RTT" (zero round trip) permettant de reprendre une connexion antérieure ;

  • Désactivation de toutes les suites cryptographiques faibles ou risquées, encore supportées par TLS 1.2. Avec TLS 1.3, vous êtes obligé d’utiliser des algorithmes forts, que ce soit pour le chiffrement, les fonctions de hachage ou les protocoles de chiffrement par blocs ;

  • Fin des clés statiques dans les échanges de clés RSA et Diffie-Hellman. La confidentialité persistante est désormais obligatoire, c’est-à-dire que les clés changent tout au long de l’échange et il n’est plus possible d’enregistrer le trafic, de retrouver la clé et de déchiffrer le trafic ensuite ;

  • Alternatives cryptographiques aux recommandations de NIST et NSA, offrant plus de confiance. Cela est dû au DualECDRBG, l’algorithme de génération de nombres pseudo-aléatoires compromis par la NSA, standardisé dans le FIPS 140-2 et largement distribué (voir "Security NSA and PRNG", "FUN NSA backdoor in OpenSSL never worked (FIPS 140-2)", "Crypto NIST removes Dual EC DRBG (NSA) from its guide", "Security Dual EC DRBG all history / NSA"). La courbe elliptique 25519 est prise en charge et présente une alternative libre aux courbes NIST et NSA ;

  • De même, l'algorithme de chiffrement symétrique libre ChaCha20 et l’asymétrique EdDSA sont supportés, afin de fournir des alternatives aux deux courbes de NIST et NSA ;

  • Exigence d'authentifier les messages chiffrés, avec en particulier 2 modes : GCM (Galois Counter Mode) et CCM (Counter with CBC-MAC). Pour plus de détails, je vous réfère au schéma de Wikipedia qui est plutôt bien fait : https://en.wikipedia.org/wiki/Galois/Counter_Mode ;

  • Et bien d'autres ajustements : optimisations des échanges, réduction de la quantité de données échangées en clair... Une autre différence longuement débattue est la possibilité d’intercepter des flux en les décryptant. Cela est tout à fait possible avec TLS 1.3, et un protocole dédié a même été ajouté : ETLS (Enterprise TLS), parfois appelé "TLS interception for grostocards".

https://www.etsi.org/deliver/etsi_ts/103500_103599/10352303/01.01.01_60/ts_10352303v010101p.pdf#page=8

Ce protocole, ou option de TLS 1.3, utilise, entre autres, une clé Diffie-Hellman statique et permet à une tierce partie de récupérer le trafic chiffré et une copie de cette clé. Pour simplifier, cela désactive la confidentialité persistante. Pour rendre les choses encore plus simples : c'est de la m**** 💩😋.

Si vous voulez faire une interception SSL/TLS propre, normale, éco-responsable et intelligente pour l'homme, faites ce que vous faisiez avant : faites passer tout le trafic par un proxy avec une autorité de certification qui signe dynamiquement des certificats (tous les proxys savent faire cela, que ce soit Bluecoat, Ironport ou Zscaler) et déployez la partie publique de cette autorité de certification dans le magasin de certificats de vos stations de travail, serveurs (qui ne devraient pas accéder directement à Internet en mode "party"), vos smartphones... comme une autorité racine de confiance.

Voici un document de Symantec sur l'interception "éthique" 😇 : https://www.symantec.com/content/dam/symantec/docs/other-resources/responsibly-intercepting-tls-and-the-impact-of-tls-1.3-en.pdf

En revanche, vous ne pourrez pas mettre d’IDS/IPS sur votre infrastructure exposée à Internet avec une réplication du trafic (TAP) pour le déchiffrer sans être dans la coupure (sauf en utilisant eTLS mais je ne vous raconte pas la galère). Franchement, l’intérêt d’un IDS/IPS dans ce cas me semble très limité si vous respectez les bonnes pratiques (mise à jour, partitionnement, audits...) et si vous avez par exemple un WAF ou équivalent portant le chiffrement (ou s’il est porté avant, comme par exemple avec un CDN).

TLS 1.3 est donc un très bon protocole, mais il avait encore deux faiblesses :

  • Pour se connecter à un service, il faut résoudre le nom de domaine, ce qui se fait avec le protocole DNS, qui n’est pas chiffré (Non, DNSSEC ne chiffre pas le DNS mais garantit seulement que l'intégrité de la réponse n'a pas été altérée) ;

  • Le nom de domaine que l'on essaie d’atteindre, situé dans le champ SNI de TLS, n'est pas chiffré, car il est présent dans la première requête du client, avant l'établissement du canal chiffré. Cette information seule (le nom de domaine) suffit à espionner un réseau Wi-Fi ou à effectuer une censure à l’échelle d’un État. Heureusement, ESNI permet de résoudre ce problème, ce que je vais détailler maintenant.

Trusted Recursive Resolver / TRR

Avant de parler de DNS over HTTPS, il faut introduire une notion simple : les résolveurs DNS de confiance (les résolveurs malveillants sont malheureusement fréquents, sans parler nécessairement de hacking). En gros, plusieurs éditeurs de navigateurs se sont associés avec des entreprises comme CloudFlare pour créer des services de résolution de noms de domaine avec la garantie qu'ils ne modifieront pas les réponses. Ainsi, le navigateur, qui utilisait auparavant le serveur DNS configuré dans le système d'exploitation, peut se passer de celui-ci et interroger directement des services de résolution DNS de confiance.

Il s’agit simplement d’une liste blanche de serveurs de confiance qui agissent comme relais pour les requêtes DNS. Ces serveurs relaient ensuite la demande DNS à la partie appropriée.

En fait... il y en a deux 😉 : https://mozilla.cloudflare-dns.com/dns-query et https://dns.google.com/experimental(https://wiki.mozilla.org/Trusted_Recursive_Resolver).

Je passe rapidement sur le fait que ces serveurs de confiance permettent (partiellement) la géolocalisation, utile pour les CDN, et, idéalement, c’est le serveur le plus proche de l’utilisateur qui est utilisé (avec un fonctionnement classique de type CDN).

DNS over HTTPS / DoH

Ce protocole, décrit dans le RFC 8484, nécessite la prise en charge de HTTP/2 et de ses flux afin de ne pas perdre trop de temps de réponse.

Il s'agit d'une encapsulation de DNS dans HTTP sur TLS. C’est donc le contenu d'une requête DNS classique qui est envoyé dans HTTP, encodé en base64 dans le cas des requêtes GET et sans encodage dans le cas des requêtes POST.

Voici un outil en Perl (désolé) effectuant ce type de requête : https://github.com/bagder/dns2doh

Sinon, il y a CURL (dans les versions récentes) :

arduino

Copier le code

~# curl -doh-url https://dns-server.example.com

Vous me direz que, pour pouvoir effectuer cette résolution de nom de domaine sur HTTPS, vous devez d'abord effectuer une requête DNS classique pour obtenir l'adresse IP correspondant au serveur TRR, ce qui n'est pas chiffré et ressemblerait au problème du "œuf et de la poule", mais au final, ce n’est que la résolution du serveur DNS, ce qui ne fuit aucune information sur vos vraies requêtes DNS. Pour avoir une solution parfaite, il faudrait coder en dur les adresses IP des serveurs, ce qui semble infaisable.

Encrypted Server Name Indication / ESNI

Pour chaque problème, il existe une solution. Il s'agit donc encore une fois d'une extension de TLS qui a résolu le problème des noms de domaine en clair lors de la connexion à un service : l'Encrypted Server Name Indication.

L’hôte ou l’entreprise souhaitant utiliser l’ESNI doit posséder un enregistrement DNS contenant une structure de données avec notamment une clé publique. À partir de cette clé publique est dérivée une clé symétrique utilisée pour chiffrer le nom de domaine dans la requête.

Notez que cette norme potentielle est encore en brouillon : https://datatracker.ietf.org/doc/draft-ietf-tls-esni/?include_text=1

Par exemple, voici l'enregistrement DNS pour CloudFlare (la structure de données en rouge est encore en base64) :

arduino

Copier le code

~# dig TXT _esni.cloudflare.org +short "/wH7nPYtACQAHQAgGFV9e448B0Nkg0dLwKX3cMwHMcJ4PX29THIg/kguXXEAAhMBAQAAAAAXWlIAAAAAABdcTEAAAA="

Pour plus de détails, j'ai trouvé peu de codes sources détaillant la décomposition de la structure, voici un exemple en Python : https://gist.githubusercontent.com/mosajjal/c088d03225287115a2e1fffef82ed25b/raw/fc37b51ac4067975a1c7e70dc0fb61a5781b078b/esni_creator.py

Puisque l’objectif est de cacher le nom du site visité, il est fortement recommandé d’utiliser une clé pour de nombreux services et non une par service. Comme vous pouvez le constater, cette fonctionnalité est particulièrement utile et défendue par les grands hébergeurs et les CDN comme CloudFront. Voici un article de CloudFlare à ce sujet : https://blog.cloudflare.com/esni/

TRR, DoH, ESNI... Tout cela complique énormément la cinématique de connexion à un site Web et repose sur peu d'acteurs, mais heureusement, il est encore possible de travailler avec le modèle ancien 😀.

0-RTT et replay de paquets

Grâce à l’optimisation de l’échange TLS, il est possible de rejouer le premier paquet TLS envoyé, à condition que l'attaquant soit capable d'intercepter le trafic (WiFi...) :

Du côté client, le navigateur signalera une erreur réseau, transparente pour l'utilisateur car elle est gérée par le navigateur, qui rejouera la requête ; Du côté serveur, cette requête spécifique sera vue deux fois. En fait, il est possible de rejouer n’importe quel paquet TLS : https://vnhacker.blogspot.com/2015/12/bad-life-advice-never-give-up-replay.html

Les risques sont limités car les cas d’exploitation sont très rares et la plupart des applications Web ajoutent des identifiants uniques qui ne peuvent pas être rejoués pour des requêtes sensibles telles que des transferts ou des paiements.

Comme le risque n’est pas nul, certains CDN comme CloudFlare ne répondent qu’à certaines requêtes 0-RTT telles que les GET sans paramètres et ajoutent un en-tête HTTP spécifique : "Cf-0rtt-Unique: value-unique-liant-and-session-key-and-nego-Tls". Pour les autres paquets, rien 😱.

Conclusion

TLS 1.3 corrige de nombreuses failles et marque un tournant en abandonnant la rétrocompatibilité avec des mécanismes obsolètes. Idéalement, configurez vos services pour n’autoriser que TLS 1.3, tout en maintenant TLS 1.2 pour éviter de bloquer certains outils ou clients.

Pour aller plus loin, regardez cette présentation (47 min) : SSTIC 2017.

Ne manquez pas notre pitch le 2 avril à 14h30 - Espace PI25

Venez nous rencontrer au Forum InCyber Lille

1er au 3 avril - stand F17-4

Rencontrez nos experts