Zvyšujeme náš server DNS cez HTTPS

Rôznych aspektov fungovania DNS sa autor už opakovane dotkol v mnohých články uverejnené ako súčasť blogu. Hlavný dôraz sa zároveň vždy kládol na zlepšenie bezpečnosti tejto kľúčovej internetovej služby.

Zvyšujeme náš server DNS cez HTTPS

Až donedávna, napriek zjavnej zraniteľnosti prevádzky DNS, ktorá je stále z väčšej časti prenášaná jasne, na škodlivé činy zo strany poskytovateľov, ktorí sa snažia zvýšiť svoj príjem vložením reklamy do obsahu, vládne bezpečnostné agentúry a cenzúra, rovnako ako jednoducho zločinci, proces posilnenie jeho ochrany, napriek prítomnosti rôznych technológií, ako sú DNSSEC/DANE, DNScrypt, DNS-over-TLS a DNS-over-HTTPS, sa zastavil. A ak sú serverové riešenia, a niektoré z nich existujú už pomerne dlho, všeobecne známe a dostupné, ich podpora zo strany klientskeho softvéru ponecháva veľa požiadaviek.

Našťastie sa situácia mení. Najmä vývojári populárneho prehliadača Firefox uviedol o plánoch na štandardné povolenie režimu podpory DNS-over-HTTPS (DoH) čoskoro. To by malo pomôcť chrániť prevádzku DNS používateľa WWW pred vyššie uvedenými hrozbami, ale potenciálne by to mohlo priniesť nové.

1. Problémy DNS-over-HTTPS

Na prvý pohľad začínajúce masové zavádzanie DNS-over-HTTPS do internetového softvéru vyvoláva len pozitívnu reakciu. Diabol sa však, ako sa hovorí, skrýva v detailoch.

Prvým problémom, ktorý obmedzuje rozsah rozšíreného používania DoH, je jeho zameranie výlučne na webovú prevádzku. Základom WWW je totiž protokol HTTP a jeho aktuálna verzia HTTP/2, na ktorej je založené DoH. Internet však nie je len web. Existuje veľa populárnych služieb, ako sú e-mail, rôzne instant messenger, systémy na prenos súborov, streamovanie multimédií atď., ktoré nevyužívajú HTTP. Napriek tomu, že mnohí z DoH to vnímajú ako všeliek, ukazuje sa, že je nepoužiteľné bez ďalšieho (a zbytočného) úsilia pre čokoľvek iné ako technológie prehliadača. Mimochodom, DNS-over-TLS vyzerá ako oveľa vhodnejší kandidát na túto rolu, ktorá implementuje zapuzdrenie štandardnej DNS prevádzky v zabezpečenom štandardnom protokole TLS.

Druhým problémom, ktorý je potenciálne oveľa závažnejší ako prvý, je skutočné upustenie od prirodzenej decentralizácie DNS podľa návrhu v prospech použitia jedného servera DoH špecifikovaného v nastaveniach prehliadača. Najmä Mozilla navrhuje použiť službu od Cloudflare. Podobnú službu spustili aj ďalšie významné osobnosti internetu, najmä Google. Ukazuje sa, že implementácia DNS-over-HTTPS v podobe, v akej sa v súčasnosti navrhuje, len zvyšuje závislosť koncových používateľov na najväčších službách. Nie je žiadnym tajomstvom, že informácie, ktoré môže poskytnúť analýza dopytov DNS, môžu o nich zhromaždiť ešte viac údajov, ako aj zvýšiť ich presnosť a relevantnosť.

V tomto smere autor bol a zostáva zástancom masovej implementácie nie DNS-over-HTTPS, ale DNS-over-TLS spolu s DNSSEC/DANE ako univerzálneho, bezpečného a neprispievajúceho k ďalšej centralizácii internetu. na zaistenie bezpečnosti prevádzky DNS. Žiaľ, z pochopiteľných dôvodov nemožno očakávať rýchle zavedenie masovej podpory alternatív DoH do klientskeho softvéru a stále je to doména nadšencov bezpečnostných technológií.

Ale keďže teraz máme DoH, prečo to nepoužiť po úteku pred potenciálnym dohľadom korporácií cez ich servery na náš vlastný server DNS-over-HTTPS?

2. Protokol DNS-over-HTTPS

Ak sa pozriete na štandard RFC8484 pri popise protokolu DNS-over-HTTPS môžete vidieť, že v skutočnosti ide o webové rozhranie API, ktoré vám umožňuje zapuzdrenie štandardného balíka DNS v protokole HTTP/2. To sa realizuje prostredníctvom špeciálnych HTTP hlavičiek, ako aj konverziou binárneho formátu prenášaných DNS údajov (viď. RFC1035 a následné dokumenty) do formy, ktorá umožňuje ich prenos a príjem, ako aj prácu s potrebnými metadátami.

Podľa štandardu je podporovaný iba HTTP/2 a zabezpečené pripojenie TLS.

Odoslanie požiadavky DNS je možné vykonať pomocou štandardných metód GET a POST. V prvom prípade sa požiadavka pretransformuje na reťazec zakódovaný v base64URL a v druhom cez telo požiadavky POST v binárnej forme. V tomto prípade sa počas požiadavky a odpovede DNS používa špeciálny typ údajov MIME aplikácia/správa 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

Venujte pozornosť aj nadpisu kontrola vyrovnávacej pamäte: v odpovedi z webového servera. V parametri max vek obsahuje hodnotu TTL pre vracaný DNS záznam (alebo minimálnu hodnotu, ak sa vracia ich množina).

Na základe vyššie uvedeného pozostáva fungovanie servera DoH z niekoľkých etáp.

  • Prijať požiadavku HTTP. Ak ide o GET, potom dekódujte paket z kódovania base64URL.
  • Pošlite tento paket na server DNS.
  • Získajte odpoveď zo servera DNS
  • Nájdite minimálnu hodnotu TTL v prijatých záznamoch.
  • Vráti odpoveď klientovi cez HTTP.

3. Váš vlastný server DNS-over-HTTPS

Najjednoduchší, najrýchlejší a najefektívnejší spôsob, ako spustiť svoj vlastný server DNS-over-HTTPS, je použiť webový server HTTP/2 H2O, o ktorej už autor stručne písal (pozri „Vysoko výkonný webový server H2O").

Túto voľbu podporuje skutočnosť, že celý kód vášho vlastného servera DoH je možné plne implementovať pomocou tlmočníka integrovaného do samotného H2O mruby. Na výmenu dát s DNS serverom potrebujete okrem štandardných knižníc aj (mrbgem) Socket knižnicu, ktorá je už našťastie zahrnutá v aktuálnej vývojovej verzii H2O 2.3.0-beta2. prítomný v portoch FreeBSD. Nie je však ťažké ho pridať do akejkoľvek predchádzajúcej verzie klonovaním úložiska Zásuvkové knižnice do katalógu /deps pred kompiláciou.

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
...

Konfigurácia webového servera je vo všeobecnosti štandardná.

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

Jedinou výnimkou je obslužný program URL /dns-dotaz za čo je v skutočnosti zodpovedný náš server DNS-over-HTTPS, napísaný v mruby a volaný cez možnosť handler mruby.handler-file.

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
}

Upozorňujeme, že v tomto prípade je za spracovanie paketov DNS zodpovedný server lokálnej vyrovnávacej pamäte neviazaný zo štandardnej distribúcie FreeBSD. Z bezpečnostného hľadiska ide o optimálne riešenie. Nič vám však nebráni vo výmene localhost na inú adresu DNS, ktorú chcete použiť.

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          *:*

Zostáva len reštartovať H2O a uvidíme, čo z toho vznikne.

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

4. Testovanie

Takže skontrolujme výsledky opätovným odoslaním testovacej požiadavky a pomocou pomôcky sa pozrieme na sieťovú prevádzku 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

Výstup ukazuje, ako žiadosť o vyriešenie adresy example.com bola prijatá a úspešne spracovaná serverom DNS.

Teraz už zostáva len aktivovať náš server v prehliadači Firefox. Aby ste to dosiahli, musíte zmeniť niekoľko nastavení na konfiguračných stránkach about: config.

Zvyšujeme náš server DNS cez HTTPS

Po prvé, toto je adresa nášho API, na ktorom bude prehliadač požadovať informácie DNS network.trr.uri. Odporúča sa tiež zadať IP domény z tejto adresy URL pre bezpečné rozlíšenie IP pomocou samotného prehliadača bez prístupu k DNS network.trr.bootstrapAddress. A nakoniec samotný parameter network.trr.mode vrátane použitia DoH. Nastavenie hodnoty na „3“ prinúti prehliadač používať na rozlíšenie názvov výlučne DNS-over-HTTPS, zatiaľ čo spoľahlivejšia a bezpečnejšia „2“ bude uprednostňovať DoH, pričom štandardné vyhľadávanie DNS zostane ako záložná možnosť.

5. ZISK!

Bol článok užitočný? Potom sa prosím nehanbite a podporte peniazmi prostredníctvom darovacieho formulára (nižšie).

Zdroj: hab.com

Pridať komentár