Oprichten van onze DNS-over-HTTPS server

De verschillende aspecten van DNS-exploitatie zijn door de auteur al meerdere keren besproken in het kader van het blog. artikelen Bij dit alles lag de belangrijkste focus altijd op het verhogen van de beveiliging van deze sleutelservice voor het hele internet.

Oprichten van onze DNS-over-HTTPS server

Tot voor kort, ondanks de duidelijkheid over de kwetsbaarheid van DNS-verkeer, dat voor het grootste deel nog steeds onversleuteld wordt verzonden, was er een proces voor kwaadaardige acties van providers die hun inkomsten wilden verhogen door advertenties in content te integreren, overheidsinstanties en censuur, evenals gewoon criminelen. De versterking van de bescherming ervan,ondanks het bestaan van verschillende technologieën zoals DNSSEC/DANE, DNScrypt, DNS-over-TLS en DNS-over-HTTPS, was traag. En terwijl serveroplossingen, waarvan sommige al geruime tijd bestaan, algemeen bekend en toegankelijk zijn, laat de ondersteuning vanuit de clientsoftware veel te wensen over.

Gelukkig verandert de situatie. In het bijzonder hebben de ontwikkelaars van de populaire browser Firefox aangegeven aangekondigd dat ze van plan zijn om binnenkort standaard ondersteuning voor DNS-over-HTTPS (DoH) in te schakelen. Dit zou moeten helpen om het DNS-verkeer van gebruikers op het WWW te beschermen tegen de eerder genoemde bedreigingen, maar kan ook nieuwe problemen oproepen.

1. Problemen met DNS-over-HTTPS

Op het eerste gezicht roept de grootschalige introductie van DNS-over-HTTPS in internetsoftware alleen maar een positieve reactie op. Maar, zoals het gezegde gaat, ligt de duivel in de details.

Het eerste probleem dat de brede toepassing van DoH beperkt, is dat het zich uitsluitend richt op webverkeer. Inderdaad, het HTTP-protocol en de huidige versie HTTP/2, waarop DoH is gebaseerd, vormen de basis van het WWW. Maar het internet is niet alleen het web. Er zijn veel populaire diensten, zoals e-mail, verschillende berichtenapps, bestandsoverdrachtssystemen, multimedia streaming, enzovoort, die geen gebruik maken van HTTP. Zo blijkt dat, ondanks dat veel mensen DoH als een panacee beschouwen, het niet toepasbaar is zonder aanvullende (toch niet noodzakelijke) inspanningen voor andere technologieën dan browsertechnologieën. Trouwens, DNS-over-TLS lijkt een veel geschiktere kandidaat voor deze rol, omdat het standaard DNS-verkeer insluit in het beveiligde standaardprotocol TLS.

Een tweede probleem, dat potentieel veel significanter is dan het eerste, is de feitelijke afschaffing van de inherente decentralisatie van DNS by design ten gunste van het gebruik van een enkele DoH-server die in de browserinstellingen is opgegeven. In het bijzonder stelt Mozilla voor om de service van Cloudflare te gebruiken. Een dergelijke service is ook gelanceerd door andere prominente spelers op het internet, waaronder Google. Het resultaat is dat de implementatie van DNS-over-HTTPS in de huidige vorm alleen maar de afhankelijkheid van eindgebruikers van de grootste diensten vergroot. Het is geen geheim dat informatie die kan worden verkregen door het analyseren van DNS-verzoeken, nog meer gegevens over hen kan verzamelen en de nauwkeurigheid en relevantie daarvan kan verhogen.

In dit verband was de auteur een voorstander van de massale invoering van niet DNS-over-HTTPS, maar DNS-over-TLS in combinatie met DNSSEC/DANE als een universeel, veilig en niet-centraliserend middel voor het waarborgen van de veiligheid van DNS-verkeer. Helaas hoeven we om begrijpelijke redenen niet snel massale ondersteuning voor alternatieven voor DoH in clientsoftware te verwachten en blijven deze voor nu voorbehouden aan enthousiastelingen van veilige technologieën.

Maar nu we DoH hebben, waarom zouden we het dan niet gebruiken, terwijl we de potentiële surveillance van bedrijven via hun servers vermijden door een eigen DNS-over-HTTPS-server op te zetten?

2. Het DNS-over-HTTPS-protocol

Als we naar de standaard kijken RFC8484 die het DNS-over-HTTPS-protocol beschrijft, zien we dat het in wezen een web-API is die de standaard DNS-pakketencapsuleert in het HTTP/2-protocol. Dit gebeurt door middel van speciale HTTP-headers, evenals de conversie van het binaire formaat van de verzonden DNS-gegevens (zie RFC1035 en de volgende documenten) naar een vorm die het mogelijk maakt om ze te verzenden en te ontvangen, evenals om met de vereiste metadata te werken.

Volgens de standaard wordt alleen HTTP/2 en een beveiligde TLS-verbinding ondersteund.

Een DNS-verzoek kan worden verzonden met de standaardmethoden GET en POST. In het eerste geval wordt het verzoek omgevormd tot een base64URL-gecodeerde string, en in de tweede via de body van het POST-verzoek in binaire vorm. Hierbij wordt een speciale MIME-gegevenssoort gebruikt voor zowel het verzoek als het antwoord van DNS. 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

Let ook op de header cache-control: in het antwoord van de webserver. In de parameter max-age staat de TTL-waarde voor het geretourneerde DNS-record (of de minimale waarde als er meerdere worden geretourneerd).

Op basis van het bovenstaande bestaat de werking van de DoH-server uit verschillende stappen.

  • Ontvang de HTTP-aanroep. Als het een GET-verzoek is, decodeer het pakket van base64URL-codering.
  • Verstuur dit pakket naar de DNS-server.
  • Ontvang een antwoord van de DNS-server.
  • Vind de minimale TTL-waarde in de ontvangen records.
  • Stuur het antwoord terug naar de client via HTTP.

3. Je eigen DNS-over-HTTPS server

De eenvoudigste, snelste en meest efficiënte manier om je eigen DNS-over-HTTPS server te starten, is door gebruik te maken van een HTTP/2 webserver H2O, waarover de auteur al kort heeft geschreven (zie "Hoge prestaties webserver H2O«).

Het voordeel van deze keuze is dat de volledige code van de eigen DoH-server volledig kan worden geïmplementeerd met de ingebouwde interpreter van H2O mruby. Naast de standaardbibliotheken is er een bibliotheek (mrbgem) Socket nodig voor het communiceren met de DNS-server, die gelukkig al is opgenomen in de huidige ontwikkelversie H2O 2.3.0-beta2. aanwezig in de FreeBSD-poorten. Het is echter niet moeilijk om deze toe te voegen in elke eerdere versie door de repository te klonen. Socket-bibliotheek in de map /deps voor de compilatie.

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 aanvaard door de gebruiker
=== h2o-2.2.6 is afhankelijk van bestand: /usr/local/sbin/pkg - gevonden
=== Alle vereiste distfiles voor h2o-2.2.6 worden gedownload
=== Extractie voor h2o-2.2.6.
=> SHA256 Checksum OK voor h2o-h2o-v2.2.6_GH0.tar.gz.
=== h2o-2.2.6 is afhankelijk van bestand: /usr/local/bin/ruby26 - gevonden
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
Klonen in ‘mruby-socket’…
remote: Bezig met het opsommen van objecten: 385, gedaan.
remote: Totaal 385 (delta 0), hergebruikt 0 (delta 0), pack-reused 385
Download van objecten: 100% (385/385), 98.02 KiB | 647.00 KiB/s, klaar.
Bepalen van wijzigingen: 100% (208/208), klaar.
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # ll
totaal 181
drwxr-xr-x   9 root  wheel  18 12 aug.  16:09 brotli/
drwxr-xr-x   2 root  wheel   4 12 aug.  16:09 cloexec/
drwxr-xr-x   2 root  wheel   5 12 aug.  16:09 golombset/
drwxr-xr-x   4 root  wheel  35 12 aug.  16:09 klib/
drwxr-xr-x   2 root  wheel   5 12 aug.  16:09 libgkc/
drwxr-xr-x   4 root  wheel  26 12 aug.  16:09 libyrmcds/
drwxr-xr-x  13 root  wheel  32 12 aug.  16:09 mruby/
drwxr-xr-x   5 root  wheel  11 12 aug.  16:09 mruby-digest/
drwxr-xr-x   5 root  wheel  10 12 aug.  16:09 mruby-dir/
drwxr-xr-x   5 root  wheel  10 12 aug.  16:09 mruby-env/
drwxr-xr-x   4 root  wheel   9 12 aug.  16:09 mruby-errno/
drwxr-xr-x   5 root  wheel  14 12 aug.  16:09 mruby-file-stat/
drwxr-xr-x   5 root  wheel  10 12 aug.  16:09 mruby-iijson/
drwxr-xr-x   5 root  wheel  11 12 aug.  16:09 mruby-input-stream/
drwxr-xr-x   6 root  wheel  11 12 aug.  16:09 mruby-io/
drwxr-xr-x   5 root  wheel  10 12 aug.  16:09 mruby-onig-regexp/
drwxr-xr-x   4 root  wheel  10 12 aug.  16:09 mruby-pack/
drwxr-xr-x   5 root  wheel  10 12 aug.  16:09 mruby-require/
drwxr-xr-x   6 root  wheel  10 12 sept. 16:10 mruby-socket/
drwxr-xr-x   2 root  wheel   9 12 aug.  16:09 neverbleed/
drwxr-xr-x   2 root  wheel  13 12 aug.  16:09 picohttpparser/
drwxr-xr-x   2 root  wheel   4 12 aug.  16:09 picotest/
drwxr-xr-x   9 root  wheel  16 12 aug.  16:09 picotls/
drwxr-xr-x   4 root  wheel   8 12 aug.  16:09 ssl-conservatory/
drwxr-xr-x   8 root  wheel  18 12 aug.  16:09 yaml/
drwxr-xr-x   2 root  wheel   8 12 aug.  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
...

De configuratie van de webserver is in het algemeen standaard.

root@beta:/usr/ports/www/h2o #  cd /usr/local/etc/h2o/
root@beta:/usr/local/etc/h2o # cat h2o.conf
# Deze voorbeeldconfiguratie geeft je een idee van hoe h2o gebruikt kan worden
# en een hoge beveiliging configuratie voor TLS en HTTP headers
# zie https://h2o.examp1e.net/ voor gedetailleerde documentatie
# en h2o --help voor opdrachtregelopties en instellingen

# v.20180207 (c)2018 door 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

Een uitzondering vormt alleen de URL-handler /dns-query waarvoor onze DNS-over-HTTPS server verantwoordelijk is, geschreven in mruby en aangeroepen via de handleroptie mruby.handler-file.

root@beta:usrlocaletch2o # cat h2odoh.rb
# H2O HTTP2 web server als DNS-over-HTTP-dienst
# 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=]/,'')
                # base64URL decoderen
                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' }, [ "Ongeldig verzoek" ]]
        else
            # --- vraag de DNS-server
            sock = UDPSocket.new
            sock.connect("localhost", 53)
            sock.send(req, 0)
            str = sock.recv(4096)
            sock.close
            # --- zoek de laagste TTL in het antwoord
            nans = str[6, 2].unpack1('n') # aantal antwoorden
            if nans > 0 # geen DNS-fout
                shift = 12
                ttl = 0
                while nans > 0
                    # verwerk domeinnaamcompressie
                    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' }, [ "Niet ondersteunde media type" ]]
    end
}

Houd er rekening mee dat de verwerking van DNS-pakketten wordt afgehandeld door de lokale caching-server, in dit geval Unbound van de standaardlevering van FreeBSD. Wat betreft veiligheid is dit een optimale oplossing. Echter, er is niets dat voorkomt dat je localhost vervangt door het adres van een andere DNS die je van plan bent te gebruiken.

root@beta:usrlocaletch2o # local-unbound versie
usage:  local-unbound [opties]
        start unbound daemon DNS-resolver.
-h      deze hulp
-c bestand configuratiebestand om te lezen in plaats van /var/unbound/unbound.conf
        bestandsindeling is beschreven in unbound.conf(5).
-d      niet op de achtergrond fork.
-p      maak geen pidfile aan.
-v      gedetailleerd (meer keren om de gedetailleerdheid te verhogen)
Versie 1.8.1
gelinkte libs: mini-event intern (het gebruikt select), OpenSSL 1.1.1a-freebsd  20 Nov 2018
gelinkte modules: dns64 respip validator iterator
BSD gelicenseerd, zie LICENSE in het bronpakket voor details.
Meld bugs aan 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          *:*

Het is tijd om H2O opnieuw op te starten en te kijken wat hier uit voortgekomen is.

root@beta:/usr/local/etc/h2o # service h2o restart
Halt h2o.
Wachten op PIDs: 69871.
Starten h2o.
start_server (pid:70532) start nu...

4. Testen

Laten we de resultaten controleren door opnieuw een testverzoek te versturen en het netwerkverkeer te bekijken met behulp van de tool. tcpdump.

root@beta/usr/local/etc/h2o # curl -H 'accept: application/dns-message' 'https://my.domain/dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE'
Waarschuwing: Binaire output kan je terminal verstoren. Gebruik "--output -" om
Waarschuwing: curl te vertellen het naar je terminal te outputten, of overweeg "--output
Waarschuwing: " om het naar een bestand te bewaren.
...
root@beta:~ # tcpdump -n -i lo0 udp port 53 -xx -XX -vv
tcpdump: luistert op lo0, link-type NULL (BSD loopback), capturegrootte 262144 bytes
16:32:40.420831 IP (tos 0x0, ttl 64, id 37575, offset 0, flags [none], proto UDP (17), lengte 57, slechte cksum 0 (->e9ea)!)
    127.0.0.1.21070 > 127.0.0.1.53: [slechte 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), lengte 73, slechte cksum 0 (->e9cb)!)
    127.0.0.1.53 > 127.0.0.1.21070: [slechte 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 pakketten gevangen
23 pakketten ontvangen door filter
0 pakketten droppen door de kernel

In de output is te zien hoe het verzoek voor adresresolutie example.com is ontvangen en succesvol verwerkt door de DNS-server.

Nu moet onze server geactiveerd worden in de Firefox-browser. Hiervoor moeten enkele instellingen op de configuratiepagina worden gewijzigd. about:config.

Oprichten van onze DNS-over-HTTPS server

Ten eerste is dit het adres van onze API waar de browser DNS-informatie zal opvragen in network.trr.uri. Het wordt ook aanbevolen om het IP-adres van dit URL op te geven voor veilige resolutie naar IP door de browser zelf zonder DNS-queries in network.trr.bootstrapAddress. En tot slot, de parameter network.trr.mode die het gebruik van DoH inschakelt. Het instellen van de waarde op «3» dwingt de browser om uitsluitend DNS-over-HTTPS te gebruiken voor het oplossen van namen, terwijl een meer betrouwbare en veilige «2» prioriteit geeft aan DoH, met standaard DNS-oproepen als back-up.

5. PROFIT!

Was dit artikel nuttig? Aarzel dan niet om financiële steun te geven via het donatieformulier (hieronder).

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster