El autor abordó repetidamente diversos aspectos del funcionamiento del DNS en varios artículos publicados como parte del blog. Al mismo tiempo, el énfasis principal siempre ha estado en mejorar la seguridad de este servicio clave para todo Internet.

Hasta hace poco, a pesar de la evidente vulnerabilidad del tráfico de DNS, que, en su mayor parte, todavía se transmite de forma clara, por acciones maliciosas de proveedores que buscan aumentar sus ingresos al incorporar publicidad en el contenido, las agencias estatales de aplicación de la ley y la censura, Además de los delincuentes, el proceso de mejorar su protección , a pesar de la presencia de diversas tecnologías, como DNSSEC / DANE, DNScrypt, DNS-over-TLS y DNS-over-HTTPS, se deslizó. Y si las soluciones de servidor, y algunas de ellas han existido durante bastante tiempo, son ampliamente conocidas y están disponibles, entonces su soporte del software del cliente deja mucho que desear.
Afortunadamente, la situación está cambiando. En particular, los desarrolladores del popular navegador Firefox anunciaron planes para habilitar el modo de soporte predeterminado para DNS sobre HTTPS (DoH) en un futuro próximo. Esto debería ayudar a proteger el tráfico DNS del usuario WWW de las amenazas mencionadas anteriormente, pero puede causar nuevas amenazas.
1. Problemas de DNS sobre HTTPS
A primera vista, el comienzo de la introducción masiva de DNS sobre HTTPS en el software que se ejecuta en Internet solo causa una reacción positiva. Sin embargo, el diablo, como dicen, está en los detalles.
El primer problema que limita el alcance del uso masivo de DoH es su enfoque únicamente en el tráfico web. De hecho, el protocolo HTTP y su versión actual de HTTP / 2, en la que se basa DoH, es la base de la WWW. Pero Internet no es solo la web. Existen muchos servicios populares, como correo electrónico, todo tipo de mensajeros, sistemas de transferencia de archivos, transmisión multimedia, etc. que no utilizan HTTP. Por lo tanto, a pesar de la percepción de muchos DoH como una panacea, resulta inaplicable sin esfuerzos adicionales (e innecesarios), por nada más que la tecnología del navegador. Por cierto, DNS sobre TLS parece un candidato mucho más digno para este rol, que implementa la encapsulación del tráfico DNS estándar en un protocolo TLS estándar seguro.
El segundo problema, que es potencialmente mucho más significativo que el primero, es el rechazo real del DNS descentralizado inherente por diseño en aras de usar el único servidor DoH especificado en la configuración del navegador. En particular, Mozilla ofrece utilizar el servicio de Cloudflare. Un servicio similar también fue lanzado por otras figuras prominentes de Internet, en particular Google. Resulta que la implementación de DNS sobre HTTPS en la forma en que se propone ahora, solo aumenta la dependencia de los usuarios finales de los servicios más grandes. No es ningún secreto que la información que puede proporcionar el análisis de consultas DNS puede recopilar aún más datos al respecto, así como aumentar su precisión y relevancia.
En este sentido, el autor fue y sigue siendo un defensor de la implementación masiva de no DNS-over-HTTPS, sino DNS-over-TLS junto con DNSSEC / DANE como una centralización universal, segura y que no promueve aún más la Internet para garantizar la seguridad del tráfico DNS. Desafortunadamente, esperar la rápida introducción de soporte masivo de alternativas DoH en el software del cliente por razones obvias, no es necesario y su destino sigue siendo entusiasta de las tecnologías seguras.
Pero dado que ahora estamos obteniendo DoH, ¿por qué no usarlo después de haber alejado de la vigilancia potencial de las corporaciones a través de sus servidores a su propio servidor DNS sobre HTTPS?
2. DNS sobre HTTPS
Si observa el estándar RFC8484 que describe el protocolo DNS sobre HTTPS, puede ver que, de hecho, es una API web que le permite encapsular un paquete DNS estándar en el protocolo HTTP / 2. Esto se implementa a través de encabezados HTTP especiales, además de convertir el formato binario de los datos DNS transmitidos (consulte RFC1035 y documentos posteriores) en un formulario que le permite enviarlos y recibirlos, así como trabajar con los metadatos necesarios.
Por norma, solo se admiten HTTP / 2 y una conexión TLS segura.
Se puede enviar una consulta DNS utilizando métodos GET y POST estándar. En el primer caso, la solicitud se transforma en una cadena codificada en base64URL, y en el segundo, a través del cuerpo de la solicitud POST en forma binaria. En este caso, cuando se consulta y responde a DNS, se utiliza una aplicación de tipo de datos MIME especial / mensaje-dns .
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
También tenga en cuenta el control de caché: encabezado en la respuesta del servidor web. El parámetro max-age contiene el valor TTL para el registro DNS devuelto (o el valor mínimo si se devuelve su conjunto).
En base a lo anterior, el funcionamiento del servidor DoH consta de varias etapas.
- Reciba una solicitud HTTP. Si es GET, decodifica el paquete de la codificación base64URL.
- Envíe este paquete al servidor DNS.
- Obtenga una respuesta del servidor DNS
- Encuentre el valor mínimo de TTL en los registros recibidos.
- Devolver la respuesta HTTP al cliente.
3. Servidor DNS-over-HTTPS propio
La forma más fácil, rápida y efectiva de iniciar su propio servidor DNS sobre HTTPS es utilizar el servidor web HTTP / 2 H2O , sobre el cual el autor ya escribió brevemente (consulte " Servidor web H2O de alto rendimiento ").
A favor de esta elección está el hecho de que todo el código de su propio servidor DoH puede implementarse completamente utilizando el intérprete mruby integrado en el propio H2O. Además de las bibliotecas estándar, para comunicarse con el servidor DNS, necesita la biblioteca Socket (mrbgem), que, afortunadamente, ya está incluida en la versión de desarrollo actual de H2O 2.3.0-beta2 presente en los puertos de FreeBSD. Sin embargo, no es difícil agregarlo a ninguna versión anterior clonando el repositorio de la biblioteca Socket en el directorio / deps antes de la compilación.
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 ...
La configuración del servidor web es generalmente estándar.
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
La única excepción es el controlador URL / dns-query , del cual es responsable nuestro servidor DNS-over-HTTPS h2odoh , escrito en mruby y llamado a través de la opción de controlador de archivos mruby.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 }
Tenga en cuenta que el servidor de almacenamiento en caché local es responsable de procesar los paquetes DNS, en este caso, Sin consolidar de la distribución estándar de FreeBSD. Desde el punto de vista de la seguridad, esta es la mejor solución. Sin embargo, nada impide reemplazar localhost con la dirección de otro DNS que intente utilizar.
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 *:*
Queda por reiniciar H2O y ver qué salió de él.
root@beta:/usr/local/etc/h2o # service h2o restart Stopping h2o. Waiting for PIDS: 69871. Starting h2o. start_server (pid:70532) starting now...
4. Prueba
Entonces, verifiquemos los resultados enviando nuevamente una solicitud de prueba y observando el tráfico de red usando la utilidad 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
El resultado muestra cómo el servidor DNS recibió y procesó con éxito la solicitud de resolución de dirección de example.com .
Ahora queda por activar nuestro servidor en el navegador Firefox. Para hacer esto, debe cambiar varios acerca de: la configuración en las páginas de configuración.

En primer lugar, esta es la dirección de nuestra API en la que el navegador consultará la información de DNS en network.trr.uri . También se recomienda que especifique la IP del dominio desde esta URL para una resolución segura en IP utilizando el navegador en sí mismo sin acceder a DNS en network.trr.bootstrapAddress . Y, finalmente, el parámetro network.trr.mode en sí, que incluye el uso de DoH. Establecer el valor en "3" forzará al navegador a usar exclusivamente DNS sobre HTTPS para resolver nombres, y el "2" más confiable y seguro dará prioridad a DoH, dejando el acceso DNS estándar como una alternativa.
5. BENEFICIOS!
¿Fue útil el artículo? Entonces, no seas tímido y apoya el dinero a través del formulario de donación (a continuación).