提升HTTPS上的DNS服务器

作者在作为博客一部分发布的许多文章中已反复提到DNS操作的各个方面。 同时,主要重点始终是提高整个互联网的该关键服务的安全性。


h


直到最近,尽管DNS流量的脆弱性很明显,但在大多数情况下,该流量仍然以明文方式传播,对于提供商试图通过在内容,州执法机构和审查制度中嵌入广告来增加收入的恶意行为,尽管存在各种技术,例如DNSSEC / DANE,DNScrypt,TLS-over-TLS和DNS-over-HTTPS,但不仅是犯罪分子, 其保护措施得到了加强 。 而且,如果服务器解决方案(其中一些已经存在了很长时间)已经广为人知且可用,那么来自客户端软件的支持将是非常不理想的。


幸运的是,情况正在发生变化。 特别是,流行的Firefox浏览器的开发人员宣布了计划在不久的将来为HTTP-over-HTTPS (DoH)启用默认支持模式的计划。 这应该有助于保护WWW用户的DNS流量免受上述威胁的侵害,但可能会导致新的威胁。



1. DNS-over-HTTPS问题


乍一看,在Internet上运行的软件中大量引入基于HTTPS的DNS的开始仅引起积极的反响。 但是,正如他们所说,魔鬼在细节中。


限制DoH大规模使用范围的第一个问题是它仅关注网络流量。 实际上,DoH所基于的HTTP协议及其当前版本的HTTP / 2是WWW的基础。 但是,互联网不仅仅是网络。 有许多不使用HTTP的流行服务,例如电子邮件,各种Messenger,文件传输系统,多媒体流等。 因此,尽管许多DoH都将其视为灵丹妙药,但如果没有其他(和不必要的)努力,事实证明它不适用,除了浏览器技术外。 顺便说一下,TLS上的DNS看起来更像是该角色的候选者,它将标准DNS流量封装到安全的标准TLS协议中。


第二个问题(可能比第一个问题严重得多)是,为了使用浏览器设置中指定的单个DoH服务器,设计实际拒绝了固有的分散DNS。 特别是,Mozilla提供使用Cloudflare的服务。 互联网的其他知名人士,尤其是谷歌,也推出了类似的服务。 事实证明,以现在提出的形式实现HTTP-over-HTTPS DNS,只会增加最终用户对最大服务的依赖性。 DNS查询分析可以提供的信息能够收集有关它的更多数据,并提高其准确性和相关性,这已不是秘密。


在这方面,作者曾经并且仍然支持不是HTTP-over-HTTPS大规模实施的支持者,而是DNS-over-TLS与DNSSEC / DANE的大规模实施的支持者,这是一种通用,安全且不会进一步促进Internet集中化的方式,以确保DNS流量的安全性。 不幸的是,由于明显的原因,可以期望在客户端软件中迅速引入DoH替代产品的大规模支持,这是没有必要的,其命运仍然是安全技术的爱好者。


但是,既然我们现在正在获得DoH,为什么在公司通过其服务器对您自己的DNS-over-HTTPS服务器进行潜在监视之后,为什么不使用它呢?


2. HTTPS上的DNS


如果查看描述HTTP-over-HTTPS协议的RFC8484标准,您会发现它实际上是一个Web API,允许您将标准DNS数据包封装到HTTP / 2协议中。 这是通过特殊的HTTP标头实现的,并将传输的DNS数据的二进制格式(请参阅RFC1035和后续文档)转换为允许您发送和接收它们以及使用必要的元数据的形式。


按照标准,仅支持HTTP / 2和安全的TLS连接。


可以使用标准的GET和POST方法发送DNS查询。 在第一种情况下,请求被转换为以base64URL编码的字符串,在第二种情况下,通过二进制形式的POST请求的主体。 在这种情况下,当查询和响应DNS时,将使用特殊的MIME数据类型application / dns-message


root@eprove:~ # curl -H 'accept: application/dns-message' 'https://my.domain/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 

还要注意Web服务器响应中的cache-control:标头。 max-age参数包含返回的DNS记录的TTL值(如果返回其设置,则为最小值)。


基于上述内容,DoH服务器的功能包括几个阶段。


  • 接收HTTP请求。 如果是GET,则从base64URL编码解码数据包。
  • 将此数据包发送到DNS服务器。
  • 取得DNS伺服器的回应
  • 在收到的记录中找到最小TTL值。
  • 将HTTP响应返回给客户端。

3.自己的基于HTTPS的DNS服务器


启动您自己的HTTP-over-HTTPS服务器的最简单,最快和最有效的方法是使用作者已经简要介绍过的HTTP / 2 H2O Web服务器(请参阅“ 高性能H2O Web服务器 ”)。


支持这一选择的事实是,可以使用集成到H2O 本身中mruby解释器来完全实现自己DoH服务器的所有代码。 除了标准库之外,要与DNS服务器通信,还需要Socket库(mrbgem),幸运的是,该库已包含在FreeBSD端口中H2O 2.3.0-beta2的当前开发版本中。 但是,通过在编译之前将Socket库存储库克隆/ deps目录中,将其添加到任何以前的版本并不难。


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

Web服务器配置通常是标准配置。


 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: 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-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 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 

唯一的例外是URL / dns-query处理程序,实际上,它负责我们的h2odoh HTTP -over-HTTPS服务器,该服务器使用mruby编写并通过mruby.handler-file 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: 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 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 } 

请注意,本地缓存服务器负责处理DNS数据包,在这种情况下,它是从标准FreeBSD发行版中解除绑定的。 从安全角度来看,这是最好的解决方案。 但是,没有什么可以阻止将本地主机替换为您打算使用的另一个DNS的地址。


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

仍然需要重新启动H2O并查看结果。


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

4.测试


因此,让我们通过再次发送测试请求并使用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 

输出显示DNS服务器如何接收example.com地址解析请求并成功对其进行处理。


现在仍然可以在Firefox浏览器中激活我们的服务器。 为此,您需要更改一些关于:配置页面上的配置设置。


Firefox DNS-over-HTTPS配置


首先,这是浏览器将在network.trr.uri中查询DNS信息的API地址。 还建议您使用浏览器本身从此URL指定域IP,以安全地解析IP,而不访问network.trr.bootstrapAddress中的 DNS。 最后是network.trr.mode参数本身,其中包括DoH的使用。 将该值设置为“ 3”将迫使浏览器仅使用HTTP-over-HTTPS来解析名称,而更可靠和安全的“ 2”将赋予DoH优先级,而将标准DNS访问作为后备。


5.利润!


这篇文章对您有帮助吗? 然后,请不要害羞,并通过捐赠表格(如下)支持这笔钱。

Source: https://habr.com/ru/post/zh-CN467237/


All Articles