使用两家服务商会增加配置难度,但可以提高 DNS 服务的稳定性。如最近的 DNSPod 宕机以及 2016 年的 Dyn 宕机所导致的众多网站无法访问,均是因为众多网站仅使用了一家 DNS 提供商。如果使用了多家 DNS 提供商,则网站仅会在所使用的所有服务商均发生宕机事故时才会无法访问,而这显然发生概率很小。
续之前的域名解析系统详解——基础篇,DNSSEC 是一组使域名解析系统(DNS)更加安全的解决方案。1993 年,IETF 展开了一个关于如何使 DNS 更加可信的公开讨论,最终决定了一个 DNS 的扩展——DNSSEC,并于 2005 年正式发布。然而,实际推行 DNSSEC 是一件非常难的事情,本文将讨论一下现有 DNS 系统所存在的一些不安全性,以及 DNSSEC 是如何解决这些问题的。
基础
传统 DNS 的问题
从上一篇文章中已经知道,在你访问一个网站,比如 www.example.com 时,浏览器发送一个 DNS 消息到一个 DNS 缓存服务器上去查询,由于 DNS 系统的庞大,这中间还需要经过好几层 DNS 缓存服务器。想要正确访问到这个网站,就需要这所有的缓存服务器都要正确的响应。
DNS 的中间人攻击
DNS 查询是明文传输的,也就是说中间人可以在传输的过程中对其更改,甚至是去自动判断不同的域名然后去做特殊处理。即使是使用其他的 DNS 缓存服务器,如 Google 的 8.8.8.8,中间人也可以直接截获 IP 包去伪造响应内容。由于我所在的国家就面临着这个问题,所以我可以轻松的给大家演示一下被中间人攻击之后是什么个情况:
1 2
$ dig +short @4.0.0.0 facebook.com 243.185.187.39
向一个没有指向任何服务器的 IP 地址:4.0.0.0 发送一个 DNS 请求,应该得不到任何响应。可是实际上在我所处的国家却返回了一个结果,很明显数据包在传输过程中“被做了手脚”。所以如果没有中间人攻击,效果是这样的:
1 2
$ dig +short @4.0.0.0 facebook.com ;; connection timed out; no servers could be reached
DNS 系统就是这么脆弱,和其他任何互联网服务一样,网络服务提供商、路由器管理员等均可以充当“中间人”的角色,来对客户端与服务器之间传送的数据包进行收集,甚至替换修改,从而导致客户端得到了不正确的信息。然而,通过一定的加密手段,可以防止中间人看到在互联网上传输的数据内容,或者可以知道原始的数据数据是否被中间人修改。
假如服务器要向客户端发送一段消息,服务器有私钥,客户端有公钥。服务器使用私钥对文本进行加密,然后传送给客户端,客户端使用公钥对其解密。由于只有服务器有私钥,所以只有服务器可以加密文本,因此加密后的文本可以认证是谁发的,并且能保证数据完整性,这样加密就相当于给记录增加了**数字签名**。但是需要注意的是,由于公钥是公开的,所以数据只是不能被篡改,但可以被监听。 此处的服务器如果是充当 DNS 服务器,那么就能给 DNS 服务带来这个特性,然而一个问题就出现了,如何传输公钥?如果公钥是使用明文传输,那么攻击者可以直接将公钥换成自己的,然后对数据篡改。
DNSSEC 这一个扩展可以为 DNS 记录添加验证信息,于是缓存服务器和客户端上的软件能够验证所获得的数据,可以得到 DNS 结果是真是假的结论。上一篇文章讲到过 DNS 解析是从根域名服务器开始,再一级一级向下解析,这与 DNSSEC 的信任链完全相同。所以部署 DNSSEC 也必须从根域名服务器开始。本文也就从根域名服务器讲起。
你发往 DNS 服务器的请求是明文的,DNS 服务器返回的请求是带签名的明文。这样 DNSSEC 只是保证了 DNS 不可被篡改,但是可以被监听,所以 DNS 不适合传输敏感信息,然而实际上的 DNS 记录几乎都不是敏感信息。HTTPS 的话会同时签名和双向加密,这样就能够传输敏感信息。 DNSSEC 的只签名,不加密主要是因为 DNSSEC 是 DNS 的一个子集,使用的是同一个端口,所以是为了兼容 DNS 而作出的东西,而 DNS 是不需要客户端与服务器建立连接的,只是客户端向服务器发一个请求,服务器再向客户端返回结果这么简单,所以 DNS 都可以使用 UDP 来传输,不需要 TCP 的握手,速度非常快。HTTPS 不是 HTTP 的子集,所以它使用的是另一个端口,为了做到加密,需要先与浏览器协商密钥,这之间进行了好几次的握手,延迟也上去了。
在哪里验证?
刚才所讲述的所有情况,都是在没有 DNS 缓存服务器的情况下。如果有 DNS 缓存服务器呢? 实际上,一些 DNS 缓存服务器就已经完成了 DNSSEC 验证,即使客户端不支持。在缓存服务器上验证失败,就直接不返回解析结果。在缓存服务器进行 DNSSEC 验证,几乎不会增加多少延迟。 但这也存在问题,如果缓存服务器到客户端之间的线路不安全呢?所以最安全的方法是在客户端上也进行一次验证,但这就会增加延迟了。
DNSSEC 的时效性和缓存
DNSSEC 相比 HTTPS 的一个特性就是 DNSSEC 是可以被缓存的,而且即使是缓存了也能验证信息的真实性,任何中间人也无法篡改。然而,既然能够缓存,就应该规定一个缓存的时长,并且这个时长也是无法篡改的。 签名是有时效性的,这样客户端才能够知道自己获得到的是最新的记录,而不是以前的记录。假如没有时效性,你的域名解析到的 IP 从 A 换到了 B,在更换之前任何人都可以轻易拿到 A 的签名。攻击者可以将 A 的签名保存下来,当你更换了 IP 后,攻击者可以继续篡改响应的 IP 为 A,并继续使用原本 A 的签名,客户端也不会察觉,这并不是所期望的。 然而在实际的 RRSIG 签名中,会包含一个时间戳(并非 UNIX 时间戳,而是一个便于阅读的时间戳),比如 20170215044626,就代表着 UTC 2017-02-15 04:46:26,这个时间戳是指这个记录的失效时间,这意味着在这个时间之后,这个签名就是无效的了。时间戳会被加进加密内容中去参与签名的计算,这样攻击者就无法更改这个时间戳。由于时间戳的存在,就限制了一个 DNS 响应可以被缓存的时长(时长就是失效时间戳减去当前时间戳)。然而,在有 DNSSEC 之前,控制缓存时长是由 TTL 决定的,所以为了确保兼容性,这两个时长应该是完全一样的。 通过这样做,即能够兼容现有的 DNS 协议,有能够保证安全,还能够利用缓存资源加快客户端的请求速度,是一个比较完美的解决方案。
会给浏览器带来一个额外的 DNS 查询时间,为了最高的安全性,浏览器应该在开始加载 HTTPS 页面(建立握手后)之前就先查询 TLSA 记录,这样才能够匹配证书是否该被信任,这样无疑增加了所有 HTTPS 页面的加载时长。
现有的 DNS 是一个不是足够稳定的东西,何况 DNSSEC 的记录类型不被一些 DNS 递归服务器所支持等,经常会由于因为种种原因遇到查询失败的问题,按照规则,TLSA 记录查询失败的话客户端也应该不加载页面。这样无疑增加了 HTTPS 页面加载的出错率,这样无疑会导致很多原本没有被中间人的网站也无法加载。
DNS 污染,在一些情况下,客户端所处的是被 DNS 污染的环境,特点就是将服务器解析到了错误的 IP 地址。然而此时中间人为了仍能够让用户访问到 HTTPS 网站,会进行类似 SNI Proxy 的操作,此时的客户端访问网站仍是比较安全的。然而显然,DANE 在 DNS 被污染后,会直接拒绝加载页面,这样被 DNS 污染的环境会有大量站点无法加载。
然而我认为这些都不是什么问题。为了解决 DNS 的问题,可以使用 Google DNS-over-HTTPS,这样的话就能避免很多 DNS 污染的问题,而且由于 DNSSEC 本身包含签名,Google 是无法对返回内容篡改的。那么直至现在,TLSA 就只存在最后一个问题:为了获取 TLSA 记录而增加的加载延迟。而这也可以完美解决,OCSP 就是一个例子,现在传统的 CA 为了实现吊销机制,也都有 OCSP:
OCSP(Online Certificate Status Protocol,在线证书状态协议)是用来检验证书合法性的在线查询服务,一般由证书所属 CA 提供。为了真正的安全,客户端会在 TLS 握手阶段进一步协商时,实时查询 OCSP 接口,并在获得结果前阻塞后续流程。OCSP 查询本质是一次完整的 HTTP 请求,导致最终建立 TLS 连接时间变得更长。
这样,在开始正式开始加载页面,客户端也需要进行一次 HTTP 请求去查询 OCSP。OCSP 也会十分影响页面加载速度,也同样会增加加载页面出错的可能。而 OCSP 有了 OCSP Stapling,这样的话 Web 服务器会预先从 CA 获取好 OCSP 的内容,在与 Web 服务器进行 HTTPS 连接时,这个内容直接返回给客户端。由于 OCSP 内容包含了签名,Web 服务器是无法造假的,所以这一整个过程是安全的。同理,TLSA 记录也可以被预存储在 Web 服务器中,在 TLS 握手阶段直接发送给客户端。这就是 DNSSEC/DANE Chain Stapling,这个想法已经被很多人提出,然而直至现在还未被列入规范。也许浏览器未来会支持 TLSA,但至少还需要很长一段时间。
传统 CA 机制所特有的优势
传统的证书配合了现在的 Certificate Transparency,即使不需要 TLSA 记录,也能一定程度上保证了证书签发的可靠性。此外,传统证书也可以使用 TLSA,其本身的安全性不比 DANE 自签名差。 此外,传统 CA 签发的证书还有自签名证书做不到的地方:
OV 以及 EV 证书:DANE 自签名的证书其实就相当于 CA 签发的 DV 证书,只要是域名所有者,就能够拥有这种证书。然而很多 CA 同时还签发 OV 和 EV 证书,OV 证书可以在证书内看到证书颁发给的组织名称,EV 则是更突出的显示在浏览器上:在 Chrome 的地址栏左侧和 HTTPS 绿锁的右侧显示组织名称;Safari 则甚至直接不显示域名而直接显示组织名称。OV 和 EV 的特点就是,你甚至都不用去思考这个域名到底是不是某个组织的,而直接看证书就能保证域名的所有者。而 DV 证书可能需要通过可靠的途径验证一下这个域名到底是不是某个组织的。比如在你找一家企业官网的时候,有可能会碰到冒牌域名的网站,而你不能通过网站是否使用了 HTTPS 而判断网站是否是冒牌的——因为冒牌的网站也可以拿到 DV 证书。而当你发现这个企业使用了 OV 或 EV 证书后,你就不用再去考虑域名是否正确,因为冒牌的网站拿不到 OV 或 EV 证书。在申请 OV 或 EV 证书时,需要向 CA 提交来自组织的申请来验证组织名称,而由于 DANE 证书是自签发的,自然也不能有 OV 或 EV 的效果。
IP 证书:CA 可以给 IP 颁发证书(尽管这很罕见),这样就可以直接访问 HTTPS 的 IP。而 DANE 基于域名系统,所以不能实现这一点
$ ssh guozeyu.com The authenticity of host 'guozeyu.com (104.199.138.99)' can't be established. ECDSA key fingerprint is SHA256:TDDVGvTLUIYr6NTZQbXITcr7mjI5eGCpqWFpXjJLUkA. Are you sure you want to continue connecting (yes/no)?
整个 DNS 具有复杂的层次,这对刚开始购买域名的人有很大的疑惑。本文将详尽的介绍 DNS 的工作原理,有助于更深刻的理解。本文将介绍:
在客户端上是如何解析一个域名的
在 DNS 缓存服务器上是如何逐级解析一个域名的
同时还包含:
域名的分类
什么是 Glue 记录
为什么 CNAME 不能设置在主域名上
先从本地的 DNS 开始讲起。
本地 DNS
本地的 DNS 相对于全球 DNS 要简单的多。所以先从本地 DNS 开始讲起。 127.0.0.1 常被用作环回 IP,也就可以作为本机用来访问自身的 IP 地址。通常,这个 IP 地址都对应着一个域名,叫 localhost。这是一个一级域名(也是顶级域名),所以和 com 同级。系统是如何实现将 localhost 对应到 127.0.0.1 的呢?其实就是通过 DNS。在操作系统中,通常存在一个 hosts 文件,这个文件定义了一组域名到 IP 的映射。常见的 hosts 文件内容如下:
1 2
127.0.0.1 localhost ::1 localhost
它就定义了 localhost 域名对应 127.0.0.1 这个 IP(第二行是 IPv6 地址)。这样,当你从浏览器里访问这个域名,或者是在终端中执行 Ping 的时候,会自动的查询这个 hosts 文件,就从文件中得到了这个 IP 地址。此外,hosts 文件还可以控制其他域名所对应的 IP 地址,并可以 override 其在全球 DNS 或本地网络 DNS 中的值。但是,hosts 文件只能控制本地的域名解析。hosts 文件出现的时候,还没有 DNS,但它可以说是 DNS 的前身。 如果需要在一个网络中,公用同一个 DNS,那么就需要使用 IP 数据包向某个服务器去获取 DNS 记录。 在一个网络里(此处主要指本地的网络,比如家庭里一个路由器下连接的所有设备和这个路由器组成的网络),会有很多主机。在与这些主机通信的时候,使用域名会更加的方便。通常,连接到同一个路由器的设备会被设置到路由器自己的一个 DNS 服务器上。这样,解析域名就不仅可以从 hosts 去获取,还可以从这个服务器上去获取。从另一个 IP 上去获取 DNS 记录通过 DNS 查询,DNS 查询通常基于 UDP 或者 TCP 这种 IP 数据包,来实现远程的查询。我的个人电脑的网络配置如下,这是在我的电脑连接了路由器之后自动就设置上的:
重点是在路由器和搜索域上。 我的电脑的主机名(也是电脑名)设置的是 ze3kr,这个内容在连接路由器时也被路由器知道了,于是路由器就给我的主机分配了一个域名 ze3kr.local,local 这个一级域名专门供本地使用。在这个网络内所有主机通过访问 ze3kr.local 这个域名时,路由器(10.0.1.1)就会告知这个域名对应的 IP 地址,于是这些主机够获得到我的电脑的 IP 地址。至于搜索域的作用,其实是可以省去输入完整的域名,比如:
1 2 3 4 5 6
$ ping ze3kr PING ze3kr.local (10.0.1.201): 56 data bytes 64 bytes from 10.0.1.201: icmp_seq=0 ttl=64 time=0.053 ms --- ze3kr.local ping statistics --- 1 packets transmitted, 1 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 0.053/0.053/0.053/0.000 ms
当设置了搜索域时,当你去解析 ze3kr 这个一级域名时,便会先尝试去解析 ze3kr.local,当发现 ze3kr.local 存在对应的解析时,便停止进一步解析,直接使用 ze3kr.local 的地址。 现在你已经了解了本地 DNS 的工作方式。DNS 的基本工作方式就是:获取域名对应 IP,然后与这个 IP 进行通信。 当在本地去获取一个完整域名(FQDN)时(执行 getaddrbyhost),通常也是通过路由器自己提供的 DNS 进行解析的。当路由器收到一个完整域名请求且没有缓存时,会继续向下一级缓存 DNS 服务器(例如运营商提供的,或者是组织提供的,如 8.8.8.8)查询,下一级缓存 DNS 服务器也没有缓存时,就会通过全球 DNS 进行查询。具体的查询方式,在 “全球 DNS” 中有所介绍。
总结
在本地,先读取本地缓存查找记录,再读取 Hosts 文件,然后在搜索域中查找域名,最后再向路由器请求 DNS 记录。
全球 DNS
在全球 DNS 中,一个完整域名通常包含多级,比如 example.com. 就是个二级域名, www.example.com. 就是个三级域名。通常我们常见到的域名都是完整的域名。
先从根域名开始,未命名根也可以作为 . 。你在接下来的部分所看到的很多域名都以 .结尾,以 .结尾的域名是特指的是根域名下的完整域名,然而不以 . 结尾的域名大都也是完整域名,实际使用时域名末尾的 .也常常省略。在本文中,我使用 dig 这一个常用的 DNS 软件来进行查询,我的电脑也已经连接到了互联网。 假设目前这个计算机能与互联网上的 IP 通信,但是完全没有 DNS 服务器。此时你需要知道根 DNS 服务器,以便自己获取某个域名的 IP 地址。根 DNS 服务器列表可以在这里下载到。文件内容如下:
其中,可以看到 TTL 与刚才文件中的内容不一样,此外还多了一个 SOA 记录,所以实际上是以这里的结果为准。这里还有一个 SOA 记录,SOA 记录是普遍存在的,具体请参考文档,在这里不做过多说明。
SOA 记录:指定有关 _DNS 区域_的权威性信息,包含主要名称服务器、域名管理员的电邮地址、域名的流水式编号、和几个有关刷新区域的定时器。(RFC 1035)
DNS 区域:对于根域名来说,DNS 区域就是空的,也就是说它负责这互联网下所有的域名。而对于我的网站,DNS 区域就是 guozeyu.com.,管理着 guozeyu.com. 本身及其子域名的记录。
此处的 ADDITIONAL SECTION 其实就包含了 Glue 记录。
一级域名
根域名自身的 DNS 服务器服务器除了被用于解析根自身之外,还用于解析所有在互联网上的一级域名。你会发现,几乎所有的 DNS 服务器,无论是否是根 DNS 服务器,都会解析其自身以及其下级域名。 从之前的解析结果中可以看出,根域名没有指定到任何 IP 地址,但是却给出了 NS 记录,于是我们就需要用这些 NS 记录来解析其下级的一级域名。下面,用所得到的根 NS 记录中的服务器其中之一来解析一个一级域名 com.。
可以看到,使用 host 命令获得 IP 地址的域名与使用 dig 获取的相同,均为 99.138.199.104.bc.googleusercontent.com. 。可以看出,这个 IP 是属于 Google 的。此外,需要注意的是 in-addr.arpa. 下的字域名正好与原 IPv4 地址排列相反。 这个记录通常可以由 IP 的所有者进行设置。然而,IP 的所有者可以将这项记录设置为任何域名,包括别人的域名。所以通过这种方法所判断其属于 Google 并不准确,所以,我们还需要对其验证:
1 2
$ dig 99.138.199.104.bc.googleusercontent.com a +short 104.199.138.99
# The following UserId and LicenseKey are required placeholders: UserId 999999 LicenseKey 000000000000 # Include one or more of the following ProductIds: # * GeoLite2-City - GeoLite 2 City # * GeoLite2-Country - GeoLite2 Country # * GeoLite-Legacy-IPv6-City - GeoLite Legacy IPv6 City # * GeoLite-Legacy-IPv6-Country - GeoLite Legacy IPv6 Country # * 506 - GeoLite Legacy Country # * 517 - GeoLite Legacy ASN # * 533 - GeoLite Legacy City ProductIds 506 GeoLite-Legacy-IPv6-Country DatabaseDirectory /usr/share/GeoIP
www.example.com 600 IN A 10.0.0.1 www.example.com 600 IN A 10.0.0.2 www.example.com 600 IN AAAA ::1 www.example.com 600 IN AAAA ::2 sub.example.com 600 IN LINK www.example.com
IP 地址就是 DNS 缓存服务器地址(如果你开启了 EDNS Client Subnet,且缓存服务器支持,那么就是自己的 IP,但是如果使用 8.8.8.8,那么会看到自己的 IP 最后一位是 0),如果你在本地指定了从你自己的服务器查,那就直接返回你自己的 IP 地址。由于我只安装了国家数据库,所以除了洲和国家之外其余都是 Unknown。
进阶使用
建立分布式 DNS
一般情况下,是一个 Master 和一个 Slave 的 DNS 解析服务器,但是这样的话对 DNSSEC 可能有问题,于是我就建立了两个 Master 服务器,自动同步记录,并设置了相同的 DNSSEC Private Key,好像并没有什么错误发生(毕竟包括 SOA 在内的所有记录也都是完全一样的),我的服务器目前的配置