J’avais promis de rédiger un article de blog sur ce sujet, et malheureusement l’actualité me contraint à avancer un peu mes projets !
Interception légale via MITM TLS
Un cas de ce qui serait de la mise en place d’interception légale de trafic par un MITM a été récemment publié. Il semble que l’hébergeur d’un opérateur de serveur XMPP/Jabber ait été contraint d’insérer une machine dans son réseau pour intercepter tout le trafic généré par le service hébergé. Mis à part l’existence de ce type de demande légale extrêmement intrusive, c’est surtout le mode opératoire qui soulève énormément de problèmes.
En effet, les services XMPP proposés étaient supposés être chiffrés via la mise-en-place de TLS, le fameux (anciennement) cadenas et barre verte dans vos navigateurs. En théorie cette protection est supposée compliquer une interception légale, en tout cas pas sans coopération, a minima information, du propriétaire du service concerné et assez rapidement visible des différents utilisateurs du service qui pourraient alors soupçonner quelque chose.
En effet, il est(ait) théoriquement impossible pour un prestataire technique, y compris par des contraintes légales, d’obtenir un certificat valide pour le service souhaité (ici jabber.ru
) sans collaboration étroite avec le propriétaire du domaine lui-même, les différentes autorités de certification allant refuser d’émettre un certificat en jabber.ru
à une entité qui n’est pas identifiée comme étant propriétaire de jabber.ru
.
Au mieux, les autorités arriveraient à délivrer un certificat en jabber.ru
qui ne serait reconnu par personne et lèverait du coup des alertes de sécurité à chaque tentative de connexion d’un client, rendant inutile l’interception de trafic.
Ici, les autorités ayant pu détourner tout le trafic de la victime, ils ont simplement été toquer à la porte de Let’s Encrypt, qui ne procède à aucune vérification humaine des demandes de certificat et se contente d’en émettre à toute personne en capacité d’effectuer des vérifications techniques de propriété du domaine.
Les autorités contrôlant finalement le contenu HTTP du domaine visé, ils ont simplement effectué une validation HTTP-01 qui se résume pour Let’s Encrypt à vérifier la présence d’un fichier http://<YOUR_DOMAIN>/.well-known/acme-challenge/<TOKEN>
… sur une machine ici sous contrôle de l’attaquant… 🤦
Il a du coup suffit aux autorités de mettre ce certificat sur leur machine d’interception, et ils ont pu déchiffrer l’intégralité du trafic TLS du service visé, sans aucune alerte de sécurité, le certificat présenté étant émis par une autorité de certification légitimement reconnue par tous les clients TLS de la planète…
Des problèmes soulevés par cette interception
On ne va pas se le cacher, interdire ce type d’interception TLS est TRÈS complexe. ’fin en fait non, assez simple mais totalement à l’opposé de toutes les pratiques « modernes » mises en œuvre actuellement dans les réseaux…
Déjà, il est assez évident que toute validation HTTP-01
est vouée à l’échec ici, puisque l’attaquant contrôle tout le trafic HTTP servi sur le domaine visé.
Il faut obligatoirement se replier sur un autre mode de validation ACME, et il se trouve que ACME supporte 2 autres protocoles, TLS-ALPN-01
et DNS-01
.
HTTP-01
vérifie la présence d’un jeton sur une URL donnée, TLS-ALPN-01
dans les entêtes TLS et DNS-01
dans la zone DNS. TLS-ALPN-01
souffre donc des mêmes problèmes de contrôle du domaine par l’attaquant que HTTP-01
: si on contrôle une machine en capacité de répondre sur du contenu HTTP ou HTTPS, c’est mort.
Ne reste donc que DNS-01
en lice. Devinez qui gère la zone DNS du service en question dans la trop grande majorité des cas ? Ah ben oui, le même prestataire que celui qui gère la machine elle-même… 🙄🤷
$ dig NS jabber.ru +short
ns2.jabber.ru.
ns1.jabber.ru.
$ dig A ns1.jabber.ru +short
139.162.179.224
$ whois 139.162.179.224
org-name: Linode, LLC
Dans tous les cas, si les autorités ont été capables d’émettre une demande d’interception de ce niveau pour la machine elle-même, ils ne sont vraiment plus à ça près d’en émettre un supplémentaire pour le prestataire DNS s’il s’avérait qu’il était finalement ailleurs (AWS par exemple)…
On pourrait se prémunir d’une attaque au niveau du DNS avec une signature DNSSec de la zone DNS, ce qui rendrait impossible l’édition de la zone DNS par l’attaquant sans faire sauter les signatures DNSSec de la zone et donc bloquerait l’émission d’un certificat compromis. Devinez qui gère la signature DNSSec dans la grande majorité des cas ? Ah ben oui, le gestionnaire de la zone DNS… qui est aussi souvent celui de la machine tout court… 🤣🤷
Stealth master DNSSec
La solution ? Ce qui en vrai aurait toujours du être déployé partout, mais que pour des arguments marketing ou d’incompétence technique a fait que DNSSec est non seulement déjà un grand mot pour beaucoup (typiquement jabber.ru
n’est pas signé alors qu’il pourrait l’être, ru
étant signé) et assez peu vérifié en pratique côté client, mais en plus est déployé n’importe comment, avec une délégation complète de la zone DNS ET de la signature DNSSec à un même prestataire. En total contradiction avec l’idée de base derrière DNSSec qui est d’imposer une double chaîne étanche entre les 2 mondes et que la personne en charge de la publication de la zone DNS ne puisse pas de son propre chef effectuer une modification dessus.
C’est à la louche mais certainement pas si éloigné de la réalité, je soupçonne que 90% des signatures DNSSec sont en réalité en pratique déléguées à l’hébergeur du DNS lui-même, ce qui revient à lui avoir filer les clefs de la maison en le faisant promettre de ne pas les utiliser sans notre accord…
Si on veut empêcher ça, il nous faut donc un système où la modification de la zone DNS ne puisse être fait que sous le contrôle de son propriétaire, et pas à la portée de l’opérateur qui lui va être chargé de publier les zones DNS au reste du monde. Et la solution existe : un stealth master DNSSec.
Les serveurs DNS faisant autorités sont séparés en 2 catégories : les masters et les slaves. Les slaves ne font que répliquer passivement via du transfert de zone (axfr
) la zone du master lors que celui-ci leur signale un changement (notify
).
En réalité ce qu’on attend d’un prestataire de DNS serait d’être exclusivement du slave, avec une zone publiée en lecture seule, modifiable exclusivement par nous-même, master. Mais il se trouve que par simplicité, on lui a accordé les deux rôles en même temps, ce qui revient à lui avoir aussi donné les droits d’écriture, ce qui n’était en aucun cas une obligation technique.
Un stealth master DNSSec revient à faire ce qu’on aurait dû toujours faire, avoir une machine maître sous notre contrôle exclusif, qui enverra les modifications à notre hébergeur DNS, opérant alors uniquement en lecture seule. D’où le master
de stealth master
.
Et le stealth
du coup ?
Mettre uniquement un master en plus ne règle pas complètement le problème de qui est en capacité de modifier la zone DNS. Mettre ce master sur une machine hébergée chez le même hébergeur que notre zone DNS n’aurait du coup pas été franchement plus utile puisque s’il voulait modifier notre zone, il aurait toujours pu le faire, DNSSec ou pas, la zone et les clefs de signature de la zone étant toujours plus ou moins à sa portée.
Il va donc falloir « planquer » ce master quelque part, et hors de portée de quiconque autre que l’administrateur lui-même. D’où le stealth
de stealth master
: tous les NS publics sont en réalité des slaves et le vrai master est invisible du public, bien à l’abri derrière un VPN, peut être positionné n’importe où dans le réseau et n’a même plus besoin d’être maintenu allumé après une modification de la zone.
DNSSec étant basé sur de la cryptographie à clef publique, ce système a aussi l’intérêt de mettre à l’abri tout le matériel cryptographique nécessaire aux signatures sur une machine avec des droits d’accès au réseau réduits au plus que strictement minimaux. Techniquement parlant ce n’est pas réellement une machine air-gapped parce que par définition, elle doit toujours pouvoir joindre les NS slave, mais on peut s’en rapprocher quand même pas mal.
Setup stealth master
Pour ma part, j’ai choisi d’héberger mon stealth master sur une petite machine dédiée, un Olimex A64, faisant tourner une Arch Linux avec Knot pour la partie DNS et Wireguard pour la partie VPN.
Knot est configuré pour s’occuper des signatures DNSSec et notifier les NS au travers du VPN. Il n’écoute que sur les ports du VPN.
/etc/knot/knot.conf
server:
listen: fd:dead::1
remote:
- id: ns1
address: fd:dead::2
- id: ns2
address: fd:dead::3
acl:
- id: acl_slave
address: fd:dead::2
address: fd:dead::3
action: [transfer, notify]
policy:
- id: ed25519
algorithm: ed25519
nsec3: true
zone:
- domain: imirhil.fr
notify: [ns1, ns2]
acl: [acl_slave]
dnssec-signing: on
dnssec-policy: ed25519
On déclare aussi le stealth master comme master sur les NS, par exemple avec bind :
/etc/bind/named.conf.local
zone "imirhil.fr" {
type slave;
file "/etc/bind/zones/slaves/fr.imirhil.db";
notify explicit; # Le stealth master n’est pas dans les NS !
masters { fd:dead::1; };
};
Le wireguard est configuré pour faire du push-only, aka que la connexion est établie exclusivement à l’initiative du stealth master, pour éviter qu’en cas de saisie des NS les connexions puissent être remontées par les autorités juste en redémarrant les machines (elles sont intégralement chiffrées de toute façon hein 🤣). Pour ça il suffit de ne pas déclarer de peer endpoint
sur les NS.
Le firewall est en mode nazi, avec exclusivement le port SSH accessible depuis une machine d’administration dans le réseau local, les IP publiques des NS autorisées pour l’accès au VPN et leur IP VPN pour les AXFR DNS.
/etc/iptables/iptables.rules
*filter
:INPUT DROP
-A INPUT -p tcp -s 192.168.0.1 --dport ssh -j ACCEPT
-A INPUT -p udp -s <public-ns1> --dport 2223 -j ACCEPT
-A INPUT -p udp -s <public-ns2> --dport 2223 -j ACCEPT
-A INPUT -p tcp -s <vpn-ns1> --dport domain -j ACCEPT
-A INPUT -p udp -s <vpn-ns1> --dport domain -j ACCEPT
-A INPUT -p tcp -s <vpn-ns2> --dport domain -j ACCEPT
-A INPUT -p udp -s <vpn-ns2> --dport domain -j ACCEPT
Le disque dur est bien entendu lui-même chiffré, rendant inutile toute saisie physique du boîtier. La connexion au SSH est faite par une clef SSH dédiée, stockée sur une Yubikey en write only. Y aurai-je aussi déployé un LUKS nuke par hasard ? 🤣
Setup Let’s Encrypt
Avec un tel setup, il suffit ensuite de déclarer dans la zone DNS, via CAA que je ne veux plus autre chose que Let’s Encrypt à pouvoir émettre des certificats pour mes domaines, et pas par autre chose que DNS-01
. J’ajoute même que seul mon compte personnel est en capacité d’émettre un certificat, et pas un autre compte Let’s Encrypt. Et merci de me signaler immédiatement par mail si une demande illicite est soumise.
$ dig CAA imirhil.fr +short
0 iodef "mailto:aeris+caa@imirhil.fr"
0 issue "letsencrypt.org;validationmethods=dns-01;accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/74301"
J’en profite aussi pour déclarer du TLSA partout dans mon DNS, histoire que si quand même un certificat finirait par être émis quelque part, personne ne puisse se connecter aux services en question (dans mes rêves seulement hein, le support de TLSA est famélique pour ne pas dire inexistant… 🙄).
$ dig TLSA _443._tcp.imirhil.fr +short
letsencrypt._tlsa.imirhil.fr.
1 1 2 13CD6017B6EB63562116363ED9ADE7C6C692823B063FC519B8861F4F 05D8943AEA8D493AA5249644752EC0B6E7FDACE09BA2B495DF2F83E5 A341641300653ACB
Il reste un tout petit problème à résoudre : DNS-01
sur une zone DNSSec ? Pas géré par un opérateur classique du marché ? 404 not found…
Sur le papier, DNS-01
est même contraire aux fondamentaux de DNSSec, à savoir fournir une validation, autant que possible humaine, lors d’une modification de la zone DNS et en tout cas interdire à des applications d’automatiquement modifier la zone DNS à leur convenance.
Parce que Let’s Encrypt impose un renouvellement des certificats tous les 90 jours, en pratique 60 si on veut se laisser une marge de manœuvre en cas d’échec.
Et il est juste hors de question de se connecter manuellement au stealth master à chaque besoin de renouvellement de certificat pour modifier la zone DNS, la resigner avec DNSSec et la publier… Surtout quand on commence à gérer une vingtaine de certificats qui n’ont pas la même date d’anniversaire, soit un renouvellement tous les 2 jours environ…
Il se trouve aussi que Certbot, l’outil de référence pour gérer ses certificats Let’s Encrypt a été conçu sur l’idée (fausse) que tout le monde allait fait la même erreur de déléguer sa zone DNS à son gestionnaire DNS… Du coup tout est prévu exclusivement :
- pour ne supporter que les grands prestataires de DNS
- en supposant que DNSSec n’existe pas
- ou en tout cas qu’il est aussi délégué au même prestataire
- et que dans tous les cas modifier la zone DNS va automagiquement resigner la zone
Alors le support d’un stealth master DNSSec auto-hébergé…
Au final, je m’en suis sorti en développant moi-même un client ACME qui passe par knsupdate
, l’outil fournit par Knot pour modifier à la volée les zones DNS.
Régulièrement, un cron se déclenche, modifie la zone DNS pour y renseigner les paramètres attendus par Let’s Encrypt, lance la signature DNSSec, publie le tout auprès des NS slaves, demande ensuite la validation auprès de Let’s Encrypt, récupère le certificat renouvelé et le pousse au travers de SSH sur les machines qui en ont besoin, puis redémarre les services. Ouf…
Ce script est livré « as is » et clairement pas testé du tout sur un setup autre que le mien 😆. Vu que je dois être 1 sur 8 milliards à faire un truc comme ça (alors que je le rappelle, tout le monde devrait en vrai le faire…), je doute qu’il soit utilisable par d’autre… Et encore, des fois je galère moi-même…
Il faut du coup penser à activer les dynamic updates dans Knot :
/etc/knot/knot.conf
acl:
- id: acl_update
address: ::1
address: 127.0.0.1
action: update
key: knsupdate
zone:
- domain: imirhil.fr
acl: [acl_update, acl_slave]
L’émission et le renouvellement d’un certificat se résume alors à :
$ letsencrypt key imirhil.fr # Génération d’une clef
$ letsencrypt csr imirhil.fr # Signature d’une CSR
$ letsencrypt crt imirhil.fr # Émission du certification à partir de la CSR
$ letsencrypt renew imirhil.fr && ./push-cert imirhil.fr # Renouvellement & déploiement
acme.sh semble avoir implémenté quelque chose d’équivalent, mais un script shell de 7648 lignes pour gérer ce type de contenu hautement sensible qu’est une PKI X509, ce n’est définitivement pas ma came.
Les limites du système
J’envisage de durcir très fortement la config à terme, avec des droits fins au niveau FS pour les certificats, restreindre les commandes SSH invocables, etc, mais c’est un chantier à part entière vu les différents services à gérer (OpenSMTPd, Postfix, Nginx, Caddy, Prosody…).
J’ai aussi envisagé de mettre une plate-forme intermédiaire centralisée, contenant les certificats, qui serait alimentée par le stealth master et dont les différents services viendraient récupérer le contenu régulièrement. Cela permettrait de réduire les accès du stealth master à une unique machine au lieu de tout le parc, mais ça suppose ensuite de développer un client « ACME light » se résumant peu ou prou à un wget && systemctl reload
à déployer partout pour synchroniser le reste du parc à partir de cette machine.
J’ai aussi un problème régulier sur mes reverse proxy IPv4 d’infra multi-tenant (typiquement le cas de PURR ou de Norore, dont j’héberge sur mes infras une VM dédiée pour eux) et sur mes infras HA redondées.
Sur les infras IPv4, c’est la même IP publique qui est accédée pour les X services de tenants différents et donc je dois mettre un reverse proxy à moi pour rediriger ensuite le trafic vers leurs services à eux. Sur les infras HA, j’ai plusieurs machines derrière un même service, avec plusieurs A/AAAA en DNS round-robin.
Une infra de prod habituelle (aka « à la yolo ») aurait X instances Certbot en mode HTTP-01
ou TLSA-ALPN-01
, une sur le HA et une sur la VM, qui chacune vivrait sa vie en émettant un certificat différent lors du renouvellement, possiblement à des dates même pas alignées. Typiquement le cas si vous utilisez du Caddy ou du Traefik. Cela rend difficile de savoir si un certificat est légitime ou non, par exemple actuellement Mozilla a deux certificats dans la nature en même temps (1 et 2).
Dans mon setup, il faudrait que lors du renouvellement du certificat fait par la machine de PURR ou de Norore, leur certificat soit aussi déployé sur mon infra par leur propre outil (certbot
, acme-pki
…). C’est juste totalement hors de question de leur laisser cette possibilité. Et à l’inverse je n’ai pas non plus envie de donner à ma propre infra trop de droits sur leurs VM ou de les faire dépendre de mes propres services DNS. Ils sont chez eux et même si techniquement je peux faire ce que je veux vu que je suis root, je minimise quand même mes intrusions possibles sur leurs infras et souhaite dans tous les cas leur permettre aussi d’utiliser un stealth master DNSSec s’ils en ont envie.
Je développe donc actuellement un système de récupération des certificats à partir des données publiques déjà disponibles à l’établissement d’une connexion TLS, en gros un équivalent plus propre d’un openssl s_client -connect <ip-vm> -servername <real-host> | openssl x509
& gestion de la chaîne de certificats. Tant que la clef privée n’est pas modifiée, le reverse ou HA proxy sera capable de maintenir le certificat à jour sans trop de latence.
Ce setup me convient à moitié, ne serait-ce que parce que cette machine, même si plus qu’isolée dans le réseau, a accès à beaucoup trop de choses pour pouvoir déployer les certificats renouvelés et relancer les services sur l’ensemble de mon infra, mais je n’ai actuellement pas d’autre solution à disposition, Let’s Encrypt s’étant développé à mon sens sans tenir compte du tout de l’écosystème DNSSec, TLSA, feu HPKP, et encore moins de ce que devrait être une infra sécurisée. Après, venant d’une autorité de certification qui considère comme « bonne pratique » de maintenir le port 80 ouvert, plus grand chose ne m’étonne il est vrai.
Le vrai problème de tout ce système est qu’on est surtout bien seul à vouloir faire les choses proprement avec un stealth master DNSSec et que du coup, il n’existe vraiment pas grand-chose comme outillage… Et que la plupart des réponses à ces vrais problèmes étaient souvent expédiées en « tu n’as qu’à faire comme tout le monde et mettre ta zone DNS chez ton hébergeur qui s’occupera aussi de ton DNSSec, tu n’auras plus de problème »…
NB : en plus de CAA qui vous notifiera en théorie si une CA voit passer une demande illégitime, pensez à déployer certspotter qui utilise les serveurs du certificate transparency pour détecter l’émission de certificats pour un de vos domaines et vous en notifier !
Comments !