DNSSEC 技术详解

tech

This article was last updated on <span id="expire-date"></span> days ago, the information described in the article may be outdated.

DNS 系统最早在 1983 年的 RFC 882 就有定义与技术实现了;作为最重要的互联网基础设施之一,我们必要对它进行足够的了解;而在如今国内复杂的网络环境下,原始的 DNS 协议也经常无法正常工作,为了保证 DNS 查询结果的可信,又诞生了 DNSSEC、DNSCrypt、DoT 甚至 DoH 这类的技术。

本文将主要介绍和讨论 DNSSEC 的工作原理、复习一些较为容易混淆的 DNS 中的概念、最后将会介绍一些 DNS 新技术。

您可能需要有对传统 DNS 协议的工作流程有基本的了解;以及对 TCP/IP 协议、数字签名技术有一个大致的了解(不用十分深入)作为知识基础。

DNSSEC

普通的 DNS 请求基于面向无状态的 UDP 协议,并且不会对响应结果进行检查,这就给了攻击者们可乘之机:最常见的 DNS 污染就是对工作在 UDP 53 端口上的流量进行 IDS 入侵检测,并直接返回一个虚假的地址。

更严重的情况是,由于 DNS Cache 的存在,错误的记录可能在很长一段时间内使得用户无法访问到真正的服务器。

这种情况下,有必要以某种方式对返回结果进行 Validation,于是就有了 DNSSEC 技术。我们首先介绍一些术语:

RRSets

RRSet 是 Resource Record Set 的简称,在这个资源集里面包含了在该域下所有的相同类型记录资源;举个例子,init.blog 域下面有三条 A 类型的记录,分别为:

  1. a.inig.blog 300 IN A 1.2.3.4
  2. b.init.blog 600 IN A 3.4.5.6
  3. c.init.blog 1200 IN A 1.0.0.1

这三条记录就组成了一个 RRSet。

Zone-Singing Key、RRSIG 与 DNSKEY

在 DNSSEC 中,每一个域都有一对公私钥对被称为 Zone-Singing Key (ZSK)。 其中的私钥用来签名上面我们提到的 RRSets;而获得的数字签名将被存储到 RRSIG 类型的 DNS 记录中。

这样 DNS 记录就可以在服务器端被签名,那么如何验证呢?

ZSK 的公钥将被存储在一个叫做 DNSKEY 的新类型 DNS 记录中。当 DNS Resolver 查询到包含 RRSIG 的记录时,就可以访问 Authorized NS 的 DNSKEY 记录,从中获取 ZSK 的公钥,用以验证签名。

下面是当我查询 init.blog 域的 MX 记录时给出的响应,可以看到,相同类型的 MX 资源组成的 RRSet 被签名,并随响应一同返回了 RRSIG:

DNSSEC RRSIG

Key-Singing Key

那么问题就来了,如何验证返回 DNSKEY 记录是否被篡改了呢?

除了 ZSK,DNSSEC 中还有一对被称为 Key-Singing Key (KSK) 的密钥对。KSK 的私钥被用来对在 DNSKEY 记录中的 ZSK 公钥进行签名,并单独为 DNSKEY 记录创建一个 RRSIG。

之后最重要的一步,我们需要将 KSK 也放入 DNSKEY 记录中。这样,在域的内部,我们就拥有了一套可信任的 DNS 查询机制。

下面是一个完整的 DNSKEY 查询响应,可以看到包含了 KSK 以及 ZSK:

DNSSEC DNSKEY

DS 记录与信任链

不可避免的,我们会有疑问:如果攻击者完全伪造了一套 KSK 与 ZSK,那我们的验证手段就依然失效了吗?

这里 DNSSEC 引入了最后一个概念,DS 记录:该类型的记录保存了域 KSK 公钥的哈希值;和上面在当前 Zone 进行验证的方式不同,这条 DS 记录被存储在上一级 Zone 中。通过每一级的 DS 记录,就可以对下一级 DNSKEY 的 RRSIG 进行验证,这样就可以构成一条信任链。

如果觉得很难理解的话,可以参考 X.509 体系中的 PKI 建设 —— DNSSEC 的验证流程其实与证书信任链十分相似,都是从顶向下的树状结构。

为什么使用 ZSK 以及 KSK

有细心的朋友可能会发现,在这个过程中,其实完全没有必要使用一套单独的 KSK;在这种情况下,我们只需要对 DNSKEY 也使用 ZSK 签名生成 RRSIG,并将 ZSK 的 Hash 作为 DS 存储即可。

那么为什么需要另外一套单独的 KSK 用来签名 DNSKEY 记录呢?

事实上,对 KSK 的任何更改都需要更新父域中的 DS 记录;而 DNSKEY 记录是会被缓存的 —— 这是为了减轻 Authorized NS 的压力;这样一来,新的 KSK 需要等到父域中旧的 DS 记录 TTL 到期之后才能被启用。

另外一方面,ZSK 将会被用来签名非常多的 RRSet,尤其是在一些庞大的域中;一旦 ZSK 的私钥出现损坏或者丢失,那么如果我们仅仅使用 ZSK,这将使得 DNS 的维护变得异常困难。

工作原理梳理

上面的过程听起来可能有些凌乱,我们自上而下的重新梳理一遍 DNSSEC 的工作流程,假设我们需要获取 init.blog 的 A 记录地址,并且该域已经启用了 DNSSEC:

  1. DNS Resolver 请求 init.blog 域的 A 记录。得到结果 IP 地址 76.223.126.88,并且同时得到了 init.blog 域中 A 记录 RRSet 的签名 RRSIG。该签名可以使用 ZSK 验证。
  2. DNS Resolver 请求 init.blog 域的 DNSKEY 记录。得到了该域的 ZSK 和 KSK 公钥,并同时得到了 DNSKEY RRSet 的签名 RRSIG。该签名可以使用 KSK 验证。
  3. DNS Resolver 使用得到的 KSK 验证了上一步得到的 DNSKEY 记录。没有发现问题。
  4. DNS Resolver 使用得到的 ZSK 验证了第一步得到的 A 记录。没有发现问题。
  5. 为了保证 DNSKEY RRSIG 中的 KSK 不被伪造,DNS Resolver 请求了 .blog 域与 init.blog 相关的 DS 记录,并且得到了 DS 记录的 RRSIG。通过计算 KSK 的 Hash 值,没有发现问题。
  6. 对 .blog 域 DS 记录的 RRSIG 重复上面 2-5 步的过程,最终通过 . 根域的 DS 验证。

在实际的递归查询过程中,该过程是自顶向下的,这里为了方便理解,我将整个过程倒过来叙述。

当我们从根域名开始查询 init.blog 的 A 记录响应时,就可以发现除根域、本域之外的任意父域都包含了子域的 DS Record,这样就可以形成一个信任链:

DNSSEC DS Trust Chain

不难发现,这样一条信任链最终回追溯到 . 根域的 DNSKEY 记录可靠性 —— 也就是根域的 KSK 可信度。这就是下面要介绍的根域签名仪式。

根域签名仪式

为了保证根域的绝对可靠性,ICANN (也就是互联网号码分配局)会每三个月进行一次新的根域签名仪式,这被称为 KSK 轮转计划

这个仪式每个季度都会在美国东西海岸轮转执行一次;而参加这个会议的人则是由 ICANN 推选出的绝对中立的互联网信任社群代表 —— TCRs。他们一共有 21 人,被分为三组:7 位成员在美国西海岸的数据中心、7 位在美国东海岸、还有 7 位是 RKSH (Recovery Key Share Holder) 恢复密钥共享持有人,来自中国的姚健康博士也是其中之一。

在每个季度的签名仪式上,都会有至少 5 个 TCRs 到场,他们需要经过各种生物信息的识别才能进入会议室,通过自己的 USB 设备在一台气隙隔离的计算机上进行签名,对具体流程感兴趣的朋友可以参考 Cloudflare 的 这篇日志

你甚至可以在 Youtube 上收看 ICANN 的签名仪式直播:Root KSK Ceremony 42 —— 因为新冠疫情的影响,可以看到大家都带上了口罩(希望疫情快点结束)。

这是根域签名仪式人员在 ICANN 的合影:

DNSSEC Root Ceremony

重启密钥系统 RKSH 的设计是 ICANN 在 2010 年提出的,当这 7 位恢复密钥共享持有人中的任意 5 位同时拿出自己的密钥(存储在一张 Smart Card 上)时,就可以恢复根密钥。当然这只会在极端情况下出现 —— 类似出现核战争导致东西海岸的 DNS 根服务系统都出现严重损坏时。希望不会有这么一天 )。

NSEC 与 NESC3

上面提到的 DNS 响应都是基于能够查询到该记录的情况 —— DNS Resolver Validation 是基于对 RRSIG 的检查;那么当不存在一条 Record 时,自然也就没有该记录的 RRSIG;如果不规定这种情况,就会给攻击者以可乘之机 —— 他们只需要返回一个固定的 NXDOMAIN Response,就可以骗过 DNS Resolver。

需要注意的是,为了保证 ZSK 的安全性以及 NS 服务器的性能,所有的签名都是事先生成的,而不是针对每个请求单独签发的。所以如何处理 “不存在” 的记录响应,就成为了一个难题。

为了解决这个问题,DNSSEC 规定了特殊的 NSEC (NextSECure) 响应,当收到查询不存在的请求时,会按照字母序返回最近一条存在的记录,并且将响应的资源记录类型指定为 NSEC,并在响应中添加该条记录的 RRSIG。

这是当查询一个不存在子域时的返回记录,可以看到响应中指明了最近一条存在记录的 NSEC 类型,并且附带了该响应的 RRSIG;除此之外,根据 RFC 7129 的描述,在响应中还需要附带该域的 SOA 以及对应的 RRSIG:

DNSSEC NSEC

但这种处理方式引发了另外一个问题:攻击者可以从 a.exmaple.com 开始进行测试,很快就可以遍历出该域下所有的子域信息 —— 这对于某些需要高安全性的站点来说也是难以接受的。

于是在 Authenticated Denial of Existence in the DNS 这篇 RFC 中除了 NSEC,还提出了一个叫做 NSEC3 的记录类型,它在原有的 NSEC 机制上进行了修改,当查询到不存在的域时,返回其 Label 的 Hash 值作为响应,这样就可以降低子域泄漏的风险:

In NSEC3, every name is hashed, including the owner name. This means that the NSEC3 chain is sorted in hash order, instead of canonical order. Because the owner names are hashed, the next owner name for “example.org” is unlikely to be “a.example.org”. Because the next owner name is hashed, zone walking becomes more difficult.

隐私与安全

DNSSEC 技术是保证了 DNS Resolver 与 Authorized NS 之间响应查询的可信度;但是可惜的是,由于这个技术的正式标准化距离现在时间并不算很长,国内大部分的 DNS Resolver 还不支持 DNSSEC;即使这项技术在国内准备大范围铺开,是否会受到相关政策阻力,也是一个未知数。

而更重要的是,在国内的网络用户如果使用国外的 DNS Resolver,其收到的解析结果也有很大几率被污染,因为 DNSSEC 并不是一个保证 Client 到 Resolver 之间通讯安全性的协议;即使使用国内某些支持 DNSSEC 的解析器,其与国外 NS 的通讯过程也会被干扰,导致用户依然无法查询到解析结果。

需要注意的是,现有的 DNSSEC 也仅仅是对查询结果的 Validation —— 也就是验证,而对于整个路由路径上的任何一个节点,你的查询都是透明的,没有 Encryption。

在这种背景下,诞生了 DNSCrypt、DoT (DNS over TLS) 甚至 DoH (DNS over HTTPS) 这样的技术,将 DNS 查询包裹在可以保证通讯安全性的协议下 —— 它们虽能够保证 Client 到 Resolver 的通讯安全,但是相应的,过多层协议的包裹带来的性能消耗也是一个不可忽略的问题。

DNS 新技术

这个部分将介绍几个 DNS 的新技术,他们大多都与这次介绍的 DNSSEC 技术相关。

EDNS

随着互联网的发展,很多复杂的业务需要在 DNS 中添加各种各样的字段,而最初的 DNS 的 UDP 包大小被限制在 512 Bytes,并且 DNS 包内部的某些字段资源也被使用的差不多了。

在这种背景下就有了 ENDS 技术,它可以通过一个虚拟的 OPT 类型的 DNS 伪资源记录,它本身并不包含 DNS 数据,也不能被转发、缓存;而是被放在 DNS 消息的 Additional Data 区域。

事实上,我们刚刚介绍的 DNSSEC 技术就依赖 EDNS 运作。

这个技术被标准化在 RFC 2671 中,在 DNS flag day 2019 之后,目前大部分的主要 DNS 解析器以及服务提供商都已经支持了这个标准。

Client Subnet in DNS Queries

这个技术则是上面说的 EDNS 除了 DNSSEC 的另一个应用;很多时候用户会将自己的 DNS Resolver 设置为类似 Google Public DNS、1.1.1.1 这类的大型公共 DNS。

而在迭代查询过程中,目标域的 Authorized NS 会接收到来自这个 “本地 DNS 服务器” 的请求,假如目标网站在全球部署了很多节点,并且会根据 DNS 请求 IP 进行调度,返回距离用户最近服务器的 IP,那么你的位置就会大概率被解析到美国加州。

这样的糟糕体验显然不是我们想要的,那怎么办呢?

有了刚刚的 EDNS 扩展,我们可以在请求中添加一个 Additional RRs,并在其中添加一个 Address 字段,将客户端的 IP/IP Subnet 信息转发给域的 Authorized NS,用以方便调度。这里为了节省篇幅就不贴出包结构了,感兴趣的朋友可以在下面的 RFC 中阅读。

该技术被标准化在 RFC 7871 中。有意思的是,该技术的主要提案者是 Google,而作为全球最大的权威 DNS 网络提供者的 CloudFlare 在他们的 1.1.1.1 服务中明确提到:

1.1.1.1 is a privacy centric resolver so it does not send any client IP information and does not send the EDNS Client Subnet Header to authoritative servers.

可以想见,该技术将使得网站拥有者在 DNS 层了解到访客的信息,而对于想要隐藏自己真实 IP 的人们来说,这显然不是一个好消息。

DNS Flatten

使用过 CloudFlare 的用户应该会注意到他们提供了一个叫做 DNS Flatten 的功能,这个功能是干什么用的呢?

现在很多的朋友喜欢将根域作为 Web 服务,而不是像古早时代的人们一样添加一条子域记录 “www” 表示万维网。而随着 CDN 的兴起,很多时候我们需要把 Web 服务使用 CNAME 映射到 CDN 提供商所提供的域上去。

这就带来了一个问题,如果我们在根域上设置了 CNAME,用户查询该域的 MX 记录时该怎么返回呢?这就产生了歧义:

  • 如果按照本域的 MX 记录提供,那么 CNAME 所代表的域别名就失去了意义
  • 如果按照 CNAME 的域返回记录,那么本域的 MX 记录即失效

事实上,MX 记录的冲突不是最严重的问题,在 DNS 的 RFC 1912RFC 2181 中规定了:

  • SOA and NS records are mandatory to be present at the root domain

  • CNAME records can only exist as single records and can not be combined with any other resource record ( DNSSEC SIG, NXT, and KEY RR records excepted)

在根域上设置 CNAME 会导致域本身失去意义,而仅仅作为一个其他域的别名,这时在 SOA 和 NS 记录中所记录的域信息则会失效。

所以 CloudFlare 以及一些比较有名的 DNS 提供商会提供这种叫做 DNS Flatten 的功能,其实就是将你的 CNAME 目标地址预先解析成一个 IP,并在用户查询根域 A 记录时返回。

其他

DNS 作为互联网最古老的协议之一,在设计之初并没有考虑到安全性的问题;而随着现在的互联网发展,它在这个方面的问题也就逐渐暴露出来。事实上,不仅仅是 DNS,TCP/IP、HTTP 等等协议都有着各种各样的安全问题,相应的,人们也设计了许许多多的新的机制对它们进行补充,以保护信息与通讯安全。

而如何让中国的互联网变得更可信、易用,事实上是一个政策问题,而不是技术问题。在各种污染、阻断、劫持、入侵检测不断的今天,除了应用日益复杂的工具,我们只能寄希望于未来会更好。

Author: 桂小方

Permalink: https://init.blog/dnssec/

文章许可协议:

如果你觉得文章对你有帮助,可以 支持我

Comments