Nous élevons notre serveur DNS sur HTTPS

Divers aspects du fonctionnement du DNS ont déjà été abordés à plusieurs reprises par l'auteur dans plusieurs ouvrages. articles publié dans le cadre du blog. Dans le même temps, l’accent a toujours été mis sur l’amélioration de la sécurité de ce service Internet clé.

Nous élevons notre serveur DNS sur HTTPS

Jusqu'à récemment, malgré la vulnérabilité évidente du trafic DNS, qui est encore en grande partie transmis en clair, aux actions malveillantes de fournisseurs cherchant à augmenter leurs revenus en intégrant de la publicité dans les contenus, des agences de sécurité gouvernementales et de la censure, ainsi que simplement les criminels, le processus renforcer sa protection, malgré la présence de diverses technologies telles que DNSSEC/DANE, DNScrypt, DNS-over-TLS et DNS-over-HTTPS, est au point mort. Et si les solutions serveur, et certaines d'entre elles existent depuis assez longtemps, sont largement connues et disponibles, leur prise en charge par les logiciels clients laisse beaucoup à désirer.

Heureusement, la situation est en train de changer. En particulier, les développeurs du populaire navigateur Firefox a déclaré à propos des projets visant à activer le mode support par défaut DNS sur HTTPS (DoH) bientôt. Cela devrait aider à protéger le trafic DNS de l'utilisateur WWW contre les menaces ci-dessus, mais pourrait potentiellement en introduire de nouvelles.

1. Problèmes DNS sur HTTPS

À première vue, l’introduction massive du DNS sur HTTPS dans les logiciels Internet ne suscite qu’une réaction positive. Cependant, comme on dit, le diable se cache dans les détails.

Le premier problème qui limite la portée de l'utilisation généralisée du DoH est sa focalisation uniquement sur le trafic Web. En effet, le protocole HTTP et sa version actuelle HTTP/2, sur laquelle se base DoH, constituent la base du WWW. Mais Internet n’est pas seulement le Web. Il existe de nombreux services populaires, tels que la messagerie électronique, diverses messageries instantanées, les systèmes de transfert de fichiers, le streaming multimédia, etc., qui n'utilisent pas HTTP. Ainsi, malgré la perception par beaucoup du DoH comme une panacée, il s’avère inapplicable sans effort supplémentaire (et inutile) pour autre chose que les technologies de navigateur. À propos, DNS-over-TLS semble être un candidat beaucoup plus intéressant pour ce rôle, qui implémente l'encapsulation du trafic DNS standard dans le protocole TLS standard sécurisé.

Le deuxième problème, potentiellement bien plus important que le premier, est l’abandon effectif de la décentralisation inhérente au DNS dès sa conception au profit de l’utilisation d’un seul serveur DoH spécifié dans les paramètres du navigateur. Mozilla suggère notamment d'utiliser un service de Cloudflare. Un service similaire a également été lancé par d'autres personnalités de l'Internet, notamment Google. Il s'avère que la mise en œuvre du DNS sur HTTPS sous la forme sous laquelle elle est actuellement proposée ne fait qu'augmenter la dépendance des utilisateurs finaux à l'égard des services les plus importants. Ce n'est un secret pour personne que les informations que l'analyse des requêtes DNS peut fournir peuvent collecter encore plus de données à ce sujet, ainsi qu'augmenter leur précision et leur pertinence.

À cet égard, l'auteur était et reste partisan de la mise en œuvre massive non pas du DNS sur HTTPS, mais du DNS sur TLS avec DNSSEC/DANE en tant que moyen universel, sécurisé et peu propice à une centralisation accrue de l'Internet. pour assurer la sécurité du trafic DNS. Malheureusement, pour des raisons évidentes, on ne peut pas s'attendre à une introduction rapide d'une prise en charge massive des alternatives DoH dans les logiciels clients, et cela reste le domaine des passionnés de technologies de sécurité.

Mais puisque nous avons désormais DoH, pourquoi ne pas l'utiliser après avoir échappé à la surveillance potentielle des entreprises via leurs serveurs vers notre propre serveur DNS sur HTTPS ?

2. Protocole DNS sur HTTPS

Si vous regardez la norme RFC8484 En décrivant le protocole DNS-over-HTTPS, vous pouvez voir qu'il s'agit en fait d'une API Web qui vous permet d'encapsuler un package DNS standard dans le protocole HTTP/2. Ceci est mis en œuvre via des en-têtes HTTP spéciaux, ainsi que par la conversion du format binaire des données DNS transmises (voir. RFC1035 et documents ultérieurs) sous une forme qui vous permet de les transmettre et de les recevoir, ainsi que de travailler avec les métadonnées nécessaires.

Selon la norme, seuls HTTP/2 et une connexion TLS sécurisée sont pris en charge.

L'envoi d'une requête DNS peut être effectué à l'aide des méthodes standard GET et POST. Dans le premier cas, la requête est transformée en une chaîne codée en base64URL, et dans le second, via le corps de la requête POST sous forme binaire. Dans ce cas, un type de données MIME spécial est utilisé lors de la requête et de la réponse DNS. application/message-dns.

root@eprove:~ # curl -H 'accept: application/dns-message' 'https://my.domaint/dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE' -v
*   Trying 2001:100:200:300::400:443...
* TCP_NODELAY set
* Connected to eprove.net (2001:100:200:300::400) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /usr/local/share/certs/ca-root-nss.crt
  CApath: none
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=my.domain
*  start date: Jul 22 00:07:13 2019 GMT
*  expire date: Oct 20 00:07:13 2019 GMT
*  subjectAltName: host "my.domain" matched cert's "my.domain"
*  issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x801441000)
> GET /dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE HTTP/2
> Host: eprove.net
> User-Agent: curl/7.65.3
> accept: application/dns-message
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Connection state changed (MAX_CONCURRENT_STREAMS == 100)!
< HTTP/2 200
< server: h2o/2.3.0-beta2
< content-type: application/dns-message
< cache-control: max-age=86274
< date: Thu, 12 Sep 2019 13:07:25 GMT
< strict-transport-security: max-age=15768000; includeSubDomains; preload
< content-length: 45
<
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.
* Failed writing body (0 != 45)
* stopped the pause stream!
* Connection #0 to host eprove.net left intact

Faites également attention au titre cache-control : dans la réponse du serveur Web. Dans le paramètre max-age contient la valeur TTL de l'enregistrement DNS renvoyé (ou la valeur minimale si un ensemble d'entre eux est renvoyé).

Sur la base de ce qui précède, le fonctionnement d'un serveur DoH comprend plusieurs étapes.

  • Recevez une requête HTTP. S'il s'agit d'un GET, décodez le paquet à partir du codage base64URL.
  • Envoyez ce paquet au serveur DNS.
  • Obtenez une réponse du serveur DNS
  • Recherchez la valeur TTL minimale dans les enregistrements reçus.
  • Renvoie une réponse au client via HTTP.

3. Votre propre serveur DNS sur HTTPS

Le moyen le plus simple, le plus rapide et le plus efficace d'exécuter votre propre serveur DNS sur HTTPS consiste à utiliser un serveur Web HTTP/2. H2O, sur lequel l'auteur a déjà écrit brièvement (voir «Serveur Web H2O haute performance").

Ce choix est soutenu par le fait que tout le code de votre propre serveur DoH peut être entièrement implémenté à l'aide de l'interpréteur intégré à H2O lui-même. mrubis. En plus des bibliothèques standards, pour échanger des données avec le serveur DNS, vous avez besoin de la bibliothèque Socket (mrbgem), qui, heureusement, est déjà incluse dans la version de développement actuelle de H2O 2.3.0-beta2. présent dans les ports FreeBSD. Cependant, il n'est pas difficile de l'ajouter à n'importe quelle version précédente en clonant le référentiel Bibliothèques de sockets cataloguer /deps avant la compilation.

root@beta:~ # uname -v
FreeBSD 12.0-RELEASE-p10 GENERIC
root@beta:~ # cd /usr/ports/www/h2o
root@beta:/usr/ports/www/h2o # make extract
===>  License MIT BSD2CLAUSE accepted by the user
===>   h2o-2.2.6 depends on file: /usr/local/sbin/pkg - found
===> Fetching all distfiles required by h2o-2.2.6 for building
===>  Extracting for h2o-2.2.6.
=> SHA256 Checksum OK for h2o-h2o-v2.2.6_GH0.tar.gz.
===>   h2o-2.2.6 depends on file: /usr/local/bin/ruby26 - found
root@beta:/usr/ports/www/h2o # cd work/h2o-2.2.6/deps/
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # git clone https://github.com/iij/mruby-socket.git
Клонирование в «mruby-socket»…
remote: Enumerating objects: 385, done.
remote: Total 385 (delta 0), reused 0 (delta 0), pack-reused 385
Получение объектов: 100% (385/385), 98.02 KiB | 647.00 KiB/s, готово.
Определение изменений: 100% (208/208), готово.
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # ll
total 181
drwxr-xr-x   9 root  wheel  18 12 авг.  16:09 brotli/
drwxr-xr-x   2 root  wheel   4 12 авг.  16:09 cloexec/
drwxr-xr-x   2 root  wheel   5 12 авг.  16:09 golombset/
drwxr-xr-x   4 root  wheel  35 12 авг.  16:09 klib/
drwxr-xr-x   2 root  wheel   5 12 авг.  16:09 libgkc/
drwxr-xr-x   4 root  wheel  26 12 авг.  16:09 libyrmcds/
drwxr-xr-x  13 root  wheel  32 12 авг.  16:09 mruby/
drwxr-xr-x   5 root  wheel  11 12 авг.  16:09 mruby-digest/
drwxr-xr-x   5 root  wheel  10 12 авг.  16:09 mruby-dir/
drwxr-xr-x   5 root  wheel  10 12 авг.  16:09 mruby-env/
drwxr-xr-x   4 root  wheel   9 12 авг.  16:09 mruby-errno/
drwxr-xr-x   5 root  wheel  14 12 авг.  16:09 mruby-file-stat/
drwxr-xr-x   5 root  wheel  10 12 авг.  16:09 mruby-iijson/
drwxr-xr-x   5 root  wheel  11 12 авг.  16:09 mruby-input-stream/
drwxr-xr-x   6 root  wheel  11 12 авг.  16:09 mruby-io/
drwxr-xr-x   5 root  wheel  10 12 авг.  16:09 mruby-onig-regexp/
drwxr-xr-x   4 root  wheel  10 12 авг.  16:09 mruby-pack/
drwxr-xr-x   5 root  wheel  10 12 авг.  16:09 mruby-require/
drwxr-xr-x   6 root  wheel  10 12 сент. 16:10 mruby-socket/
drwxr-xr-x   2 root  wheel   9 12 авг.  16:09 neverbleed/
drwxr-xr-x   2 root  wheel  13 12 авг.  16:09 picohttpparser/
drwxr-xr-x   2 root  wheel   4 12 авг.  16:09 picotest/
drwxr-xr-x   9 root  wheel  16 12 авг.  16:09 picotls/
drwxr-xr-x   4 root  wheel   8 12 авг.  16:09 ssl-conservatory/
drwxr-xr-x   8 root  wheel  18 12 авг.  16:09 yaml/
drwxr-xr-x   2 root  wheel   8 12 авг.  16:09 yoml/
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # cd ../../..
root@beta:/usr/ports/www/h2o # make install clean
...

La configuration du serveur web est généralement standard.

root@beta:/usr/ports/www/h2o #  cd /usr/local/etc/h2o/
root@beta:/usr/local/etc/h2o # cat h2o.conf
# this sample config gives you a feel for how h2o can be used
# and a high-security configuration for TLS and HTTP headers
# see https://h2o.examp1e.net/ for detailed documentation
# and h2o --help for command-line options and settings

# v.20180207 (c)2018 by Max Kostikov http://kostikov.co e-mail: [email protected]

user: www
pid-file: /var/run/h2o.pid
access-log:
    path: /var/log/h2o/h2o-access.log
    format: "%h %v %l %u %t "%r" %s %b "%{Referer}i" "%{User-agent}i""
error-log: /var/log/h2o/h2o-error.log

expires: off
compress: on
file.dirlisting: off
file.send-compressed: on

file.index: [ 'index.html', 'index.php' ]

listen:
    port: 80
listen:
    port: 443
    ssl:
        cipher-suite: ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
        cipher-preference: server
        dh-file: /etc/ssl/dhparams.pem
        certificate-file: /usr/local/etc/letsencrypt/live/eprove.net/fullchain.pem
        key-file: /usr/local/etc/letsencrypt/live/my.domain/privkey.pem

hosts:
    "*.my.domain":
        paths: &go_tls
            "/":
                redirect:
                    status: 301
                    url: https://my.domain/
    "my.domain:80":
        paths: *go_tls
    "my.domain:443":
        header.add: "Strict-Transport-Security: max-age=15768000; includeSubDomains; preload"
        paths:
            "/dns-query":
               mruby.handler-file: /usr/local/etc/h2o/h2odoh.rb

La seule exception est le gestionnaire d'URL / dns-query dont notre serveur DNS-over-HTTPS, écrit en mruby et appelé via l'option handler, est réellement responsable fichier mruby.handler.

root@beta:/usr/local/etc/h2o # cat h2odoh.rb
# H2O HTTP/2 web server as DNS-over-HTTP service
# v.20190908 (c)2018-2019 Max Kostikov https://kostikov.co e-mail: [email protected]

proc {|env|
    if env['HTTP_ACCEPT'] == "application/dns-message"
        case env['REQUEST_METHOD']
            when "GET"
                req = env['QUERY_STRING'].gsub(/^dns=/,'')
                # base64URL decode
                req = req.tr("-_", "+/")
                if !req.end_with?("=") && req.length % 4 != 0
                    req = req.ljust((req.length + 3) & ~3, "=")
                end
                req = req.unpack1("m")
            when "POST"
                req = env['rack.input'].read
            else
                req = ""
        end
        if req.empty?
            [400, { 'content-type' => 'text/plain' }, [ "Bad Request" ]]
        else
            # --- ask DNS server
            sock = UDPSocket.new
            sock.connect("localhost", 53)
            sock.send(req, 0)
            str = sock.recv(4096)
            sock.close
            # --- find lowest TTL in response
            nans = str[6, 2].unpack1('n') # number of answers
            if nans > 0 # no DNS failure
                shift = 12
                ttl = 0
                while nans > 0
                    # process domain name compression
                    if str[shift].unpack1("C") < 192
                        shift = str.index("x00", shift) + 5
                        if ttl == 0 # skip question section
                            next
                        end
                    end
                    shift += 6
                    curttl = str[shift, 4].unpack1('N')
                    shift += str[shift + 4, 2].unpack1('n') + 6 # responce data size
                    if ttl == 0 or ttl > curttl
                        ttl = curttl
                    end
                    nans -= 1
                 end
                 cc = 'max-age=' + ttl.to_s
            else
                 cc = 'no-cache'
            end
            [200, { 'content-type' => 'application/dns-message', 'content-length' => str.size, 'cache-control' => cc }, [ str ] ]
        end
    else
        [415, { 'content-type' => 'text/plain' }, [ "Unsupported Media Type" ]]
    end
}

Veuillez noter que le serveur de mise en cache local est responsable du traitement des paquets DNS, dans ce cas Non lié de la distribution standard FreeBSD. D'un point de vue sécurité, c'est la solution optimale. Cependant, rien ne vous empêche de remplacer localhost à une autre adresse DNS que vous comptez utiliser.

root@beta:/usr/local/etc/h2o # local-unbound verison
usage:  local-unbound [options]
        start unbound daemon DNS resolver.
-h      this help
-c file config file to read instead of /var/unbound/unbound.conf
        file format is described in unbound.conf(5).
-d      do not fork into the background.
-p      do not create a pidfile.
-v      verbose (more times to increase verbosity)
Version 1.8.1
linked libs: mini-event internal (it uses select), OpenSSL 1.1.1a-freebsd  20 Nov 2018
linked modules: dns64 respip validator iterator
BSD licensed, see LICENSE in source package for details.
Report bugs to [email protected]
root@eprove:/usr/local/etc/h2o # sockstat -46 | grep unbound
unbound  local-unbo 69749 3  udp6   ::1:53                *:*
unbound  local-unbo 69749 4  tcp6   ::1:53                *:*
unbound  local-unbo 69749 5  udp4   127.0.0.1:53          *:*
unbound  local-unbo 69749 6  tcp4   127.0.0.1:53          *:*

Il ne reste plus qu'à redémarrer H2O et voir ce que cela donne.

root@beta:/usr/local/etc/h2o # service h2o restart
Stopping h2o.
Waiting for PIDS: 69871.
Starting h2o.
start_server (pid:70532) starting now...

4. Tests

Vérifions donc les résultats en envoyant à nouveau une demande de test et en examinant le trafic réseau à l'aide de l'utilitaire. tcpdump.

root@beta/usr/local/etc/h2o # curl -H 'accept: application/dns-message' 'https://my.domain/dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE'
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.
...
root@beta:~ # tcpdump -n -i lo0 udp port 53 -xx -XX -vv
tcpdump: listening on lo0, link-type NULL (BSD loopback), capture size 262144 bytes
16:32:40.420831 IP (tos 0x0, ttl 64, id 37575, offset 0, flags [none], proto UDP (17), length 57, bad cksum 0 (->e9ea)!)
    127.0.0.1.21070 > 127.0.0.1.53: [bad udp cksum 0xfe38 -> 0x33e3!] 43981+ A? example.com. (29)
        0x0000:  0200 0000 4500 0039 92c7 0000 4011 0000  ....E..9....@...
        0x0010:  7f00 0001 7f00 0001 524e 0035 0025 fe38  ........RN.5.%.8
        0x0020:  abcd 0100 0001 0000 0000 0000 0765 7861  .............exa
        0x0030:  6d70 6c65 0363 6f6d 0000 0100 01         mple.com.....
16:32:40.796507 IP (tos 0x0, ttl 64, id 37590, offset 0, flags [none], proto UDP (17), length 73, bad cksum 0 (->e9cb)!)
    127.0.0.1.53 > 127.0.0.1.21070: [bad udp cksum 0xfe48 -> 0x43fa!] 43981 q: A? example.com. 1/0/0 example.com. A 93.184.216.34 (45)
        0x0000:  0200 0000 4500 0049 92d6 0000 4011 0000  ....E..I....@...
        0x0010:  7f00 0001 7f00 0001 0035 524e 0035 fe48  .........5RN.5.H
        0x0020:  abcd 8180 0001 0001 0000 0000 0765 7861  .............exa
        0x0030:  6d70 6c65 0363 6f6d 0000 0100 01c0 0c00  mple.com........
        0x0040:  0100 0100 0151 8000 045d b8d8 22         .....Q...].."
^C
2 packets captured
23 packets received by filter
0 packets dropped by kernel

La sortie montre comment la demande de résolution de l'adresse example.com a été reçu et traité avec succès par le serveur DNS.

Il ne reste plus qu'à activer notre serveur dans le navigateur Firefox. Pour ce faire, vous devez modifier plusieurs paramètres sur les pages de configuration about: config.

Nous élevons notre serveur DNS sur HTTPS

Tout d'abord, il s'agit de l'adresse de notre API à laquelle le navigateur demandera des informations DNS dans réseau.trr.uri. Il est également recommandé de spécifier l'adresse IP du domaine à partir de cette URL pour une résolution IP sécurisée à l'aide du navigateur lui-même sans accéder au DNS dans réseau.trr.bootstrapAddress. Et enfin, le paramètre lui-même réseau.trr.mode y compris l’utilisation du DoH. Définir la valeur sur « 3 » forcera le navigateur à utiliser exclusivement DNS sur HTTPS pour la résolution de noms, tandis que le « 2 », plus fiable et sécurisé, donnera la priorité à DoH, laissant la recherche DNS standard comme option de secours.

5. PROFITEZ !

L'article a-t-il été utile ? Alors ne soyez pas timide et soutenez financièrement via le formulaire de don (ci-dessous).

Source: habr.com

Ajouter un commentaire