Stiamo lanciando il nostro server DNS-over-HTTPS

Vari aspetti dell'operatività del DNS sono già stati trattati dall'autore in diversi articoli pubblicati nel blog. In questo contesto, l'accento è sempre stato posto sul miglioramento della sicurezza di questo servizio fondamentale per tutta Internet.

Stiamo lanciando il nostro server DNS-over-HTTPS

Fino a poco tempo fa, nonostante l'evidente vulnerabilità del traffico DNS, che è ancora per lo più trasmesso in chiaro, per azioni malevole da parte dei provider, che cercano di aumentare i loro guadagni integrando pubblicità nei contenuti, dalle autorità governative e dalla censura, così come da parte di semplici criminali, il processo di potenziamento della sua protezione, nonostante la presenza di diverse tecnologie come DNSSEC/DANE, DNScrypt, DNS-over-TLS e DNS-over-HTTPS, è stato lento. E sebbene le soluzioni server, alcune delle quali esistono da molto tempo, siano ampiamente conosciute e disponibili, il supporto da parte del software client lascia molto a desiderare.

Fortunatamente, la situazione è in cambiamento. In particolare, gli sviluppatori del popolare browser Firefox hanno annunciato piani per l'inclusione per default della modalità DNS-over-HTTPS (DoH) a breve. Questo dovrebbe contribuire a proteggere il traffico DNS degli utenti WWW dalle minacce sopra menzionate, ma potrebbe potenzialmente causare nuovi problemi.

1. Problemi con DNS-over-HTTPS

A prima vista, l'adozione massiccia di DNS-over-HTTPS nel software che opera in Internet suscita solo reazioni positive. Tuttavia, come si suol dire, il diavolo è nei dettagli.

Il primo problema che limita l'adozione su larga scala di DoH è la sua focalizzazione esclusiva sul traffico web. Infatti, il protocollo HTTP e la sua versione attuale HTTP/2, su cui si basa DoH, costituiscono la base del WWW. Tuttavia, Internet non è solo web. Ci sono molti servizi popolari, come l'email, vari messaggeri, sistemi di trasferimento file, streaming multimediale e altro, che non utilizzano HTTP. Pertanto, nonostante che molti considerino DoH una panacea, esso risulta inapplicabile senza sforzi aggiuntivi (e non necessari) per niente di diverso dalle tecnologie browser. A proposito, DNS-over-TLS appare come un candidato molto più valido per questo ruolo, poiché implementa l'incapsulamento del traffico DNS standard all'interno del protocollo standard TLS protetto.

Un'altra problematica che è potenzialmente molto più significativa della prima è il reale abbandono della decentralizzazione intrinseca del DNS per favorire l'utilizzo di un unico server DoH configurato nelle impostazioni del browser. In particolare, Mozilla consiglia di utilizzare il servizio di Cloudflare. Anche altre importanti realtà del web, come Google, hanno lanciato servizi simili. Di conseguenza, l'implementazione del DNS-over-HTTPS come proposta attualmente accresce la dipendenza degli utenti finali dai maggiori fornitori di servizi. Non è un segreto che l'analisi delle richieste DNS possa raccogliere ulteriori dati su di loro e migliorarne la precisione e la rilevanza.

In questo contesto, l'autore è stato e rimane un sostenitore dell'adozione massiccia di DNS-over-TLS insieme a DNSSEC/DANE come soluzione universale, sicura e che non promuove la centralizzazione di Internet per garantire la sicurezza del traffico DNS. Sfortunatamente, non ci si può aspettare un rapido implemento di un supporto diffuso per alternative a DoH nel software client, per motivi comprensibili, e attualmente spetta solo agli appassionati delle tecnologie sicure.

Ma, dato che ora stiamo ottenendo DoH, perché non utilizzarlo, allontanandoci dal potenziale monitoraggio da parte delle corporazioni tramite i loro server verso il proprio server DNS-over-HTTPS?

2. Protocollo DNS-over-HTTPS

Se si guarda allo standard RFC8484 che descrive il protocollo DNS-over-HTTPS, si può vedere che esso rappresenta, fondamentalmente, un'API web che consente di incapsulare un pacchetto DNS standard nel protocollo HTTP/2. Questo viene realizzato tramite intestazioni HTTP speciali e convertendo il formato binario dei dati DNS trasmessi (vedi RFC1035 e documenti successivi) in una forma che consente di trasmetterli e riceverli, nonché di lavorare con i metadati necessari.

Il supporto standard è solo per HTTP/2 e connessioni sicure TLS.

L'invio di una richiesta DNS può avvenire tramite i metodi standard GET e POST. Nel primo caso, la richiesta viene trasformata in una stringa codificata base64URL, mentre nel secondo — attraverso il corpo della richiesta POST in forma binaria. In questo caso, durante la richiesta e la risposta DNS viene utilizzato un tipo MIME speciale. application/dns-message.

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: " to save to a file.
* Failed writing body (0 != 45)
* stopped the pause stream!
* Connection #0 to host eprove.net left intact

Si prega di notare anche il titolo cache-control: nella risposta del server web. Nel parametro max-age si trova il valore TTL per il record DNS restituito (o il valore minimo se viene restituito un insieme di essi).

Di conseguenza, il funzionamento del server DoH si compone di diverse fasi.

  • Ricevere la richiesta HTTP. Se si tratta di un GET, decodificare il pacchetto dalla codifica base64URL.
  • Inviare questo pacchetto al server DNS.
  • Ricevere la risposta dal server DNS.
  • Trovare il valore TTL minimo nei record ricevuti.
  • Restituire al cliente la risposta via HTTP.

3. Il tuo server DNS-over-HTTPS

Il modo più semplice, veloce ed efficace per avviare il proprio server DNS-over-HTTPS è utilizzare un server web HTTP/2 H2O, di cui l'autore ha già scritto brevemente (vedi “Server web ad alte prestazioni H2O«).

A supportare questa scelta c'è il fatto che tutto il codice del proprio server DoH può essere completamente implementato utilizzando l'interprete integrato in H2O mruby. Oltre alle librerie standard, per lo scambio di dati con il server DNS è necessaria la libreria (mrbgem) Socket, che, fortunatamente, è già inclusa nella versione attuale per sviluppatori H2O 2.3.0-beta2. presente in FreeBSD ports. Tuttavia, non è difficile aggiungerla anche in qualsiasi versione precedente clonando il repository librerie Socket nella directory /deps prima della compilazione.

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 accettata dall'utente
=== h2o-2.2.6 dipende dal file: /usr/local/sbin/pkg - trovato
=== Recupero tutti i file necessari per h2o-2.2.6 per la compilazione
=== Estrazione di h2o-2.2.6.
=> Controllo SHA256 OK per h2o-h2o-v2.2.6_GH0.tar.gz.
=== h2o-2.2.6 dipende dal file: /usr/local/bin/ruby26 - trovato
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
Clonazione in "mruby-socket"…
remote: Enumerando oggetti: 385, fatto.
remote: Totale 385 (delta 0), riutilizzati 0 (delta 0), pack-reused 385
Ricezione oggetti: 100% (385/385), 98.02 KiB | 647.00 KiB/s, pronto.
Determinazione delle modifiche: 100% (208/208), pronto.
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # ll
total 181
drwxr-xr-x   9 root  wheel  18 12 ago.  16:09 brotli/
drwxr-xr-x   2 root  wheel   4 12 ago.  16:09 cloexec/
drwxr-xr-x   2 root  wheel   5 12 ago.  16:09 golombset/
drwxr-xr-x   4 root  wheel  35 12 ago.  16:09 klib/
drwxr-xr-x   2 root  wheel   5 12 ago.  16:09 libgkc/
drwxr-xr-x   4 root  wheel  26 12 ago.  16:09 libyrmcds/
drwxr-xr-x  13 root  wheel  32 12 ago.  16:09 mruby/
drwxr-xr-x   5 root  wheel  11 12 ago.  16:09 mruby-digest/
drwxr-xr-x   5 root  wheel  10 12 ago.  16:09 mruby-dir/
drwxr-xr-x   5 root  wheel  10 12 ago.  16:09 mruby-env/
drwxr-xr-x   4 root  wheel   9 12 ago.  16:09 mruby-errno/
drwxr-xr-x   5 root  wheel  14 12 ago.  16:09 mruby-file-stat/
drwxr-xr-x   5 root  wheel  10 12 ago.  16:09 mruby-iijson/
drwxr-xr-x   5 root  wheel  11 12 ago.  16:09 mruby-input-stream/
drwxr-xr-x   6 root  wheel  11 12 ago.  16:09 mruby-io/
drwxr-xr-x   5 root  wheel  10 12 ago.  16:09 mruby-onig-regexp/
drwxr-xr-x   4 root  wheel  10 12 ago.  16:09 mruby-pack/
drwxr-xr-x   5 root  wheel  10 12 ago.  16:09 mruby-require/
drwxr-xr-x   6 root  wheel  10 12 set. 16:10 mruby-socket/
drwxr-xr-x   2 root  wheel   9 12 ago.  16:09 neverbleed/
drwxr-xr-x   2 root  wheel  13 12 ago.  16:09 picohttpparser/
drwxr-xr-x   2 root  wheel   4 12 ago.  16:09 picotest/
drwxr-xr-x   9 root  wheel  16 12 ago.  16:09 picotls/
drwxr-xr-x   4 root  wheel   8 12 ago.  16:09 ssl-conservatory/
drwxr-xr-x   8 root  wheel  18 12 ago.  16:09 yaml/
drwxr-xr-x   2 root  wheel   8 12 ago.  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 configurazione del server web è, in generale, standard.

root@beta:/usr/ports/www/h2o #  cd /usr/local/etc/h2o/
root@beta:/usr/local/etc/h2o # cat h2o.conf
# questo esempio di configurazione ti dà un'idea di come può essere utilizzato h2o
# e di una configurazione ad alta sicurezza per TLS e intestazioni HTTP
# vedi https://h2o.examp1e.net/ per documentazione dettagliata
# e h2o --help per le opzioni e impostazioni da riga di comando

# v.20180207 (c)2018 di Max Kostikov http://kostikov.co e-mail: max@kostikov.co

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

L'unica eccezione è il gestore URL /dns-query di cui è responsabile il nostro server DNS-over-HTTPS, scritto in mruby e richiamato tramite l'opzione del gestore mruby.handler-file.

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

proc {|env|
    if env['HTTP_ACCEPT'] == "application/dns-message"
        case env['REQUEST_METHOD']
            when "GET"
                req = env['QUERY_STRING'].gsub(/^dns=/,'')
                # decodifica base64URL
                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' }, [ "Richiesta Non Valida" ]]
        else
            # --- chiedi al server DNS
            sock = UDPSocket.new
            sock.connect("localhost", 53)
            sock.send(req, 0)
            str = sock.recv(4096)
            sock.close
            # --- trova il TTL più basso nella risposta
            nans = str[6, 2].unpack1('n') # numero di risposte
            if nans > 0 # nessun errore DNS
                shift = 12
                ttl = 0
                while nans > 0
                    # processa la compressione del nome di dominio
                    if str[shift].unpack1("C")  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' }, [ "Tipo di Media Non Supportato" ]]
    end
}

Si prega di notare che il trattamento dei pacchetti DNS è gestito dal server di caching locale, in questo caso Unbound dalla fornitura standard di FreeBSD. In termini di sicurezza, è una soluzione ottimale. Tuttavia, nulla impedisce di sostituire localhost con l'indirizzo di un altro DNS che si prevede di utilizzare.

root@beta:/usr/local/etc/h2o # local-unbound version
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 unbound-bugs@nlnetlabs.nl
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          *:*

Rimane da riavviare H2O e vedere cosa è successo.

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

4. Test

Quindi, verifichiamo i risultati inviando nuovamente una richiesta di prova e osservando il traffico di rete utilizzando l'utility 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: " 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

Nell'output si può vedere come la richiesta per la risoluzione dell'indirizzo example.com sia stata ricevuta e elaborata con successo dal server DNS.

Ora è rimasto solo da attivare il nostro server nel browser Firefox. Per fare ciò, è necessario modificare alcune impostazioni nella pagina di configurazione. about:config.

Stiamo lanciando il nostro server DNS-over-HTTPS

Innanzitutto, questo è l'indirizzo della nostra API da cui il browser richiederà le informazioni DNS a network.trr.uri. È consigliabile anche specificare l'IP del dominio di questo URL per una risoluzione sicura in IP direttamente dal browser, senza fare riferimento al DNS in network.trr.bootstrapAddress. Infine, il parametro stesso network.trr.mode attiva l'uso di DoH. Impostare il valore a "3" forzerà il browser a utilizzare esclusivamente DNS-over-HTTPS per la risoluzione dei nomi, mentre un'opzione più sicura e affidabile "2" darà priorità a DoH, mantenendo la tradizionale interrogazione DNS come opzione di riserva.

5. PROFITTO!

L'articolo è stato utile? Non esitate a supportarci con una donazione tramite il modulo qui sotto.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster