Różne aspekty eksploatacji DNS były już wielokrotnie poruszane przez autora w opublikowanych na blogu. Główny nacisk zawsze kładziony był na zwiększenie bezpieczeństwa tej kluczowej usługi internetowej.

Do niedawna, pomimo oczywistości podatności ruchu DNS, który wciąż w większości jest przesyłany w otwartym formacie, dla złowrogich działań ze strony dostawców usług, starających się zwiększyć swoje dochody poprzez wstawianie reklam w treści, organów rządowych i cenzury, a także zwykłych przestępców, proces , pomimo istnienia różnych technologii, takich jak DNSSEC/DANE, DNScrypt, DNS-over-TLS i DNS-over-HTTPS, utknął w martwym punkcie. I choć rozwiązania serwerowe, z których niektóre istnieją już od dłuższego czasu, są szeroko znane i dostępne, wsparcie ich ze strony oprogramowania klienckiego pozostawia wiele do życzenia.
Na szczęście sytuacja się zmienia. Szczególnie, deweloperzy popularnej przeglądarki Firefox plany dotyczące włączenia domyślnego wsparcia dla (DoH) w najbliższym czasie. Powinno to pomóc w ochronie ruchu DNS użytkowników WWW przed wyżej wymienionymi zagrożeniami, jednak potencjalnie może wywołać nowe.
1. Problemy z DNS-over-HTTPS
Na pierwszy rzut oka, zaczynające się masowe wprowadzenie DNS-over-HTTPS w oprogramowaniu działającym w Internecie budzi tylko pozytywne reakcje. Jednak diabeł, jak to się mówi, tkwi w szczegółach.
Pierwszym problemem, który ogranicza masowe zastosowanie DoH, jest jego koncentracja wyłącznie na ruchu webowym. Rzeczywiście, protokół HTTP oraz jego aktualna wersja HTTP/2, na której oparty jest DoH, stanowią fundament WWW. Jednak Internet to nie tylko sieć webowa. Istnieje wiele popularnych usług, takich jak e-mail, różnorodne komunikatory, systemy transferu plików, strumieniowanie multimediów i inne, które nie korzystają z protokołu HTTP. W związku z tym, pomimo iż wiele osób postrzega DoH jako uniwersalne rozwiązanie, okazuje się, że jest on nieprzydatny bez dodatkowych (a nawet zbędnych) wysiłków w przypadku technologii innych niż technologie przeglądarkowe. Warto zauważyć, że bardziej odpowiednim kandydatem do tej roli wydaje się DNS-over-TLS, który realizuje enkapsulację standardowego ruchu DNS w zabezpieczonym protokole TLS.
Drugim problemem, który potencjalnie jest znacznie bardziej istotny niż pierwszy, jest faktyczne zaniechanie decentralizacji DNS według projektu na rzecz korzystania z jednego serwera DoH wskazanego w ustawieniach przeglądarki. W szczególności Mozilla zaleca korzystanie z usługi Cloudflare. Podobny serwis wprowadziły również inne znaczące postacie w Internecie, w tym Google. Okazuje się, że wdrożenie DNS-over-HTTPS w obecnej formie zwiększa jedynie zależność końcowych użytkowników od największych usługodawców. Nie jest tajemnicą, że informacje, które można uzyskać z analizy zapytań DNS, mogą gromadzić jeszcze więcej danych o użytkownikach oraz zwiększyć ich dokładność i aktualność.
W tym kontekście autor był i pozostaje zwolennikiem powszechnego wdrażania nie DNS-over-HTTPS, a DNS-over-TLS w połączeniu z DNSSEC/DANE jako uniwersalnego, bezpiecznego narzędzia, które nie sprzyja dalszej centralizacji Internetu w zapewnieniu bezpieczeństwa ruchu DNS. Niestety, nie można oczekiwać szybkiego wdrożenia powszechnego wsparcia dla alternatyw DoH w oprogramowaniu klienckim z oczywistych względów, a jego domeną pozostają jak na razie entuzjaści technologii zabezpieczeń.
Jednak, skoro teraz mamy DoH, to dlaczego by go nie wykorzystać, wcześniej unikając potencjalnego śledzenia ze strony korporacji poprzez ich serwery na własny serwer DNS-over-HTTPS?
2. Protokół DNS-over-HTTPS
Jeśli spojrzymy na standard opisujący protokół DNS-over-HTTPS, to można zobaczyć, że w istocie stanowi on web API umożliwiające inkapsulację standardowego pakietu DNS w protokole HTTP/2. Realizuje się to poprzez specjalne nagłówki HTTP oraz konwersję binarnego formatu przesyłanych danych DNS (patrz i dokumenty pokrewne) na formę, która pozwala je przesyłać i odbierać, a także pracować z niezbędnymi metadanymi.
Standardowo obsługiwany jest tylko HTTP/2 oraz bezpieczne połączenie TLS.
Wysyłanie zapytania DNS może odbywać się standardowymi metodami GET i POST. W pierwszym przypadku zapytanie jest transformowane na zakodowany w base64URL ciąg, a w drugim — przez ciało zapytania POST w formie binarnej. Przy tym w zapytaniach i odpowiedziach DNS używany jest specjalny typ MIME. application/dns-message.
root@eprove:~ # curl -H 'accept: application/dns-message' 'https://my.domaint/dns-query?dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE' -v
* Próba połączenia z 2001:100:200:300::400:443...
* TCP_NODELAY ustawiono
* Połączono z eprove.net (2001:100:200:300::400) port 443 (#0)
* ALPN, oferuje h2
* ALPN, oferuje http/1.1
* pomyślnie ustalono lokalizacje weryfikacji certyfikatu:
* CAfile: /usr/local/share/certs/ca-root-nss.crt
CApath: brak
* 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, Certyfikat (11):
* TLSv1.3 (IN), TLS handshake, Weryfikacja certyfikatu (15):
* TLSv1.3 (IN), TLS handshake, Zakończono (20):
* TLSv1.3 (OUT), TLS zmień szyfr, Zmień specyfikację szyfru (1):
* TLSv1.3 (OUT), TLS handshake, Zakończono (20):
* Połączenie SSL za pomocą TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, serwer zaakceptował użycie h2
* Certyfikat serwera:
* subject: CN=my.domain
* data rozpoczęcia: Jul 22 00:07:13 2019 GMT
* data wygaśnięcia: Oct 20 00:07:13 2019 GMT
* subjectAltName: host "my.domain" pasuje do certyfikatu "my.domain"
* issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3
* Weryfikacja certyfikatu SSL zakończona sukcesem.
* Używanie HTTP2, serwer obsługuje wielokrotne użycie
* Stan połączenia zmieniono (HTTP/2 potwierdzone)
* Kopiowanie danych HTTP/2 z bufora strumieniowego do bufora połączenia po aktualizacji: len=0
* Używanie ID strumienia: 1 (łatwe uchwyt 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, Nowa sesja Ticket (4):
* Stan połączenia zmieniono (MAX_CONCURRENT_STREAMS == 100)!
< HTTP/2 200
< serwer: h2o/2.3.0-beta2
< content-type: application/dns-message
< cache-control: max-age=86274
< data: Thu, 12 Sep 2019 13:07:25 GMT
< strict-transport-security: max-age=15768000; includeSubDomains; preload
< content-length: 45
<
Ostrzeżenie: Wyjście binarne może zepsuć Twój terminal. Użyj "--output -" aby
Ostrzeżenie: kazać curlowi wyprowadzić to do Twojego terminala, lub rozważ "--output
Ostrzeżenie: " aby zapisać do pliku.
* Zapis ciała zakończony niepowodzeniem (0 != 45)
* Zatrzymano strumień pauzy!
* Połączenie #0 z hostem eprove.net pozostało nienaruszoneZwróć również uwagę na nagłówek cache-control: w odpowiedzi od serwera WWW. W parametrze max-age znajduje się wartość TTL dla zwracanego wpisu DNS (lub wartość minimalna, jeśli zwracana jest ich grupa).
Na podstawie powyższego działanie serwera DoH składa się z kilku etapów.
- Odbierz żądanie HTTP. Jeśli to GET, dekoduj pakiet z kodowania base64URL.
- Wyślij ten pakiet do serwera DNS.
- Odbierz odpowiedź od serwera DNS
- Znajdź minimalną wartość TTL w otrzymanych wpisach.
- Zwróć odpowiedź do klienta przez HTTP.
3. Własny serwer DNS-over-HTTPS
Najprostszym, najszybszym i najefektywniejszym sposobem uruchomienia własnego serwera DNS-over-HTTPS jest wykorzystanie serwera WWW HTTP/2 , o którym autor już wcześniej pisał (zob. „«).
Na korzyść tego wyboru przemawia fakt, że cały kod własnego serwera DoH można całkowicie zaimplementować za pomocą wbudowanego w H2O interpretera . Oprócz standardowych bibliotek, do wymiany danych z serwerem DNS potrzebna jest biblioteka (mrbgem) Socket, która, na szczęście, jest już wbudowana w aktualną wersję deweloperską H2O 2.3.0-beta2 w portach FreeBSD. Niemniej jednak, łatwo jest dodać ją również w każdej poprzedniej wersji, klonując repozytorium w katalogu /deps przed kompilacją.
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
Klonowanie do „mruby-socket”…
remote: Enumerating objects: 385, done.
remote: Total 385 (delta 0), reused 0 (delta 0), pack-reused 385
Otrzymanie obiektów: 100% (385/385), 98.02 KiB | 647.00 KiB/s, gotowe.
Określenie zmian: 100% (208/208), gotowe.
root@beta:/usr/ports/www/h2o/work/h2o-2.2.6/deps # ll
total 181
drwxr-xr-x 9 root wheel 18 12 sie 16:09 brotli/
drwxr-xr-x 2 root wheel 4 12 sie 16:09 cloexec/
drwxr-xr-x 2 root wheel 5 12 sie 16:09 golombset/
drwxr-xr-x 4 root wheel 35 12 sie 16:09 klib/
drwxr-xr-x 2 root wheel 5 12 sie 16:09 libgkc/
drwxr-xr-x 4 root wheel 26 12 sie 16:09 libyrmcds/
drwxr-xr-x 13 root wheel 32 12 sie 16:09 mruby/
drwxr-xr-x 5 root wheel 11 12 sie 16:09 mruby-digest/
drwxr-xr-x 5 root wheel 10 12 sie 16:09 mruby-dir/
drwxr-xr-x 5 root wheel 10 12 sie 16:09 mruby-env/
drwxr-xr-x 4 root wheel 9 12 sie 16:09 mruby-errno/
drwxr-xr-x 5 root wheel 14 12 sie 16:09 mruby-file-stat/
drwxr-xr-x 5 root wheel 10 12 sie 16:09 mruby-iijson/
drwxr-xr-x 5 root wheel 11 12 sie 16:09 mruby-input-stream/
drwxr-xr-x 6 root wheel 11 12 sie 16:09 mruby-io/
drwxr-xr-x 5 root wheel 10 12 sie 16:09 mruby-onig-regexp/
drwxr-xr-x 4 root wheel 10 12 sie 16:09 mruby-pack/
drwxr-xr-x 5 root wheel 10 12 sie 16:09 mruby-require/
drwxr-xr-x 6 root wheel 10 12 wrz 16:10 mruby-socket/
drwxr-xr-x 2 root wheel 9 12 sie 16:09 neverbleed/
drwxr-xr-x 2 root wheel 13 12 sie 16:09 picohttpparser/
drwxr-xr-x 2 root wheel 4 12 sie 16:09 picotest/
drwxr-xr-x 9 root wheel 16 12 sie 16:09 picotls/
drwxr-xr-x 4 root wheel 8 12 sie 16:09 ssl-conservatory/
drwxr-xr-x 8 root wheel 18 12 sie 16:09 yaml/
drwxr-xr-x 2 root wheel 8 12 sie 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
...Konfiguracja serwera WWW jest w zasadzie standardowa.
root@beta:/usr/ports/www/h2o # cd /usr/local/etc/h2o/
root@beta:/usr/local/etc/h2o # cat h2o.conf
# ten przykładowy plik konfiguracyjny pokazuje, jak można używać h2o
# oraz konfigurację o wysokim poziomie bezpieczeństwa dla TLS i nagłówków HTTP
# zobacz https://h2o.examp1e.net/ dla szczegółowej dokumentacji
# oraz h2o --help dla opcji i ustawień wiersza poleceń
# v.20180207 (c)2018 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.rbJedynym wyjątkiem jest obsługa URL /dns-query za którą odpowiada nasz serwer DNS-over-HTTPS, napisany w mruby i wywoływany przez opcję obsługi mruby.handler-file.
root@beta:</usr/local/etc/h2o # cat h2odoh.rb
# H2O HTTP/2 serwer WWW jako usługa 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=/,'')
# dekodowanie 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' }, [ "Złe żądanie" ]]
else
# --- zapytanie do serwera DNS
sock = UDPSocket.new
sock.connect("localhost", 53)
sock.send(req, 0)
str = sock.recv(4096)
sock.close
# --- znajdź najniższy TTL w odpowiedzi
nans = str[6, 2].unpack1('n') # liczba odpowiedzi
if nans > 0 # brak błędu DNS
shift = 12
ttl = 0
while nans > 0
# przetwarzanie kompresji nazw domen
if str[shift].unpack1("C") < 192
shift = str.index("x00", shift) + 5
if ttl == 0 # pomiń sekcję zapytania
next
end
end
shift += 6
curttl = str[shift, 4].unpack1('N')
shift += str[shift + 4, 2].unpack1('n') + 6 # rozmiar danych odpowiedzi
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' }, [ "Typ mediów nieobsługiwany" ]]
end
}Zwróć uwagę, że za przetwarzanie pakietów DNS odpowiada lokalny serwer pamięci podręcznej, w tym przypadku z domyślnej instalacji FreeBSD. Z perspektywy bezpieczeństwa to optymalne rozwiązanie. Jednak nic nie stoi na przeszkodzie, aby zmienić localhost na adres innego serwera DNS, który zamierzasz użyć.
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 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 *:*Należy zrestartować H2O i zobaczyć, co z tego wynikło.
root@beta:/usr/local/etc/h2o # service h2o restart
Stopping h2o.
Waiting for PIDS: 69871.
Starting h2o.
start_server (pid:70532) starting now...4. Testowanie
Zatem sprawdźmy wyniki, wysyłając ponownie próbne zapytanie i obserwując ruch sieciowy za pomocą narzędzia 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 kernelW zjeździe widać, jak zapytanie o rozwiązanie adresu example.com zostało odebrane i pomyślnie przetworzone przez serwer DNS.
Teraz wystarczy aktywować nasz serwer w przeglądarce Firefox. W tym celu na stronie konfiguracji należy zmienić kilka ustawień about:config.

Po pierwsze, to adres naszego API, z którego przeglądarka będzie żądać informacji DNS w network.trr.uri. Zaleca się także wskazanie adresu IP domeny z tego URL, aby zapewnić bezpieczne rozwiązywanie do IP za pomocą samej przeglądarki bez kontaktu z DNS w network.trr.bootstrapAddress. A na koniec, właściwy parametr network.trr.mode włączający użycie DoH. Ustawienie wartości na '3' wymusi na przeglądarce korzystanie wyłącznie z DNS-over-HTTPS do rozwiązywania nazw, a bardziej niezawodne i bezpieczne '2' nada priorytet DoH, pozostawiając standardowe zapytanie DNS jako opcję zapasową.
5. ZYSK!
Czy artykuł był przydatny? Proszę nie wahać się i wesprzeć nas finansowo przez formularz darowizny (poniżej).
Źródło: habr.com
