为什么DoH可以保护你的浏览隐私

DNS的查询是明文的,你所使用网络的提供者或者网络的中经过的任何一个环节的机器,都可以根据DNS查询信息得到你想访问的域名.

而DoH将DNS包在HTTPS请求中,网络提供者无法从传输的流量包中查看HTTPS内的DNS请求.当然了HTTPS安全的前提是在客户端的的证书信任设置无问题无法进行中间人攻击.

所以使用DoH后,无法从DoH请求中得到所浏览网站域名.

为什么是现在

浏览器的支持

最早是firefox在2018,到edge/chrome从2020年开始也都支持了DoH了,几大浏览器均已支持.https://en.wikipedia.org/wiki/DNS_over_HTTPS#Web_browsers

今年(2021年)以来的封锁

firefox一开始推出时候,可以直接使用,但现在在几个浏览器中内置的各大DoH服务商的选项均已不可用.

搭建过程

搭建自己的DoH服务,低调仅自己用,本质上也只是一个普通的HTTPS网站,不会被封.

选择

需求:

  • 提供DoH
  • 用加密方式,最好是也是DoH,再向其它大的DNS服务请求
  • 支持docker部署,能简单配置就上线
  • 用的人多

最后选择coreDNS,查了文档向后请求不支持DoH,但支持DoT同样能达到效果.

其它依赖

域名,拿域名服务商提供的API token给certbot使用,用certbot申请letsencrypt证书,在服务机器上装好dockerdocker-compose

配置

摘取ansible中的其中两个关键文件:

ansible/roles/coredns/templates/docker-compose.yml.j2

version: '2.2'
services:
  coredns:
    image: 'coredns/coredns:1.8.4'
    restart: always
    hostname: '{{SERVICE_FQDN}}'
    ports:
      - '0.0.0.0:80:80'
      - '0.0.0.0:443:443'
      - '0.0.0.0:53:53'
      - '0.0.0.0:53:53/udp'
    volumes:
      - '{{COREDNS_HOME}}/Corefile:/Corefile'
      - '/etc/letsencrypt/live/{{SERVICE_FQDN}}/fullchain.pem:/etc/coredns/ssl/{{SERVICE_FQDN}}.crt'
      - '/etc/letsencrypt/live/{{SERVICE_FQDN}}/privkey.pem:/etc/coredns/ssl/{{SERVICE_FQDN}}.key'

ansible/roles/coredns/templates/Corefile.j2

.:53 {
    forward . 8.8.8.8:53
    log
}

https://.:443 {
    forward . 127.0.0.1:5301 127.0.0.1:5302
    log
    tls /etc/coredns/ssl/{{SERVICE_FQDN}}.crt /etc/coredns/ssl/{{SERVICE_FQDN}}.key
}

.:5301 {
    forward . 8.8.8.8 8.8.4.4 {
        tls_servername dns.google
    }
}

.:5302 {
    forward . 1.1.1.1 1.0.0.1 {
        tls_servername cloudflare-dns.com
    }
}

成功运行后,服务运行在https://{{SERVICE_FQDN}},在浏览器中填写的是https://{{SERVICE_FQDN}}/dns-query

注意本文中的双大括号是j2模板文件中的,都需要替换为具体变量.

重点是DoH服务,53端口不用的话,测试成功运行后不需要暴露.

测试

DoH请求需要经过base64后再拼接,可直接参考使用:https://help.aliyun.com/document_detail/171664.html中的python脚本测试DoH服务.

把下面的dns.alidns.com改成服务域名SERVICE_FQDN,即可测试.

import dns.message
import requests
import base64
import json

doh_url = "https://dns.alidns.com/dns-query"
domain = "alibaba.com"
rr = "A"
result = []

message = dns.message.make_query(domain, rr)
dns_req = base64.b64encode(message.to_wire()).decode("UTF8").rstrip("=")
r = requests.get(doh_url + "?dns=" + dns_req,
                 headers={"Content-type": "application/dns-message"})
for answer in dns.message.from_wire(r.content).answer:
    dns = answer.to_text().split()
    result.append({"Query": dns[0], "TTL": dns[1], "RR": dns[3], "Answer": dns[4]})
    print(json.dumps(result))

几个坑

Corefile中的HTTPS服务定义

一定不要加上域名.写成

https://{{SERVICE_FQDN}}:443 {
    forward . 127.0.0.1:5301 127.0.0.1:5302
    log
    tls /etc/coredns/ssl/{{SERVICE_FQDN}}.crt /etc/coredns/ssl/{{SERVICE_FQDN}}.key
}

这样的话,服务是起来后运行不正常.要把域名换成一个点.

DNS的服务

如果测试时候要测试DNS的53端口,UDP53端口要一起暴露出来,否则DNS的53服务不正常.

不过不用DNS 53的服务的话,这个也不用管,只是试着运行时候被这个坑到了,以为转发有问题.

藏好了吗?

不是,如果你是访问的http的网站, 或者是混合内容的网站,也会暴露浏览的域名.

就算你访问的是纯HTTPS的网站,建立HTTPS的过程中的SNI,也明文暴露了浏览域名.

SNI_leak_domain

因此还需要使用ss,换成在ss服务端暴露这些信息.如果ss服务端的网络提供者不想得到你的浏览信息,你就相对安全了.

或者用洋葱头.

参考

https://coredns.io/manual/configuration/

https://github.com/coredns/coredns/issues/3507#issuecomment-622247383

https://help.aliyun.com/document_detail/171664.html