章 强, 彭 飞
(扬州职业大学, 江苏 扬州 225009)
域名系统(以下简称DNS)是互联网的核心基础设施,它是一个分布式的分层数据库,存储A、MX、AAAA、CNAME等资源记录。整个DNS被分为三层:根、顶级域名(TLD)和各级权威DNS服务器。DNS因此成为互联网重要的基础设施之一。也正是这个原因,DNS服务器一直是互联网被攻击的主要目标,攻击方法如放大攻击、缓存中毒攻击、DDoS攻击、DNS劫持等[1-2]。
DNS协议被认为是一种安全的通信协议,连接到互联网的每台计算机都信任从其所查询DNS权威服务器收到的名称解析结果。但是,DNS数据都是通过明文发送的,在一个递归名称解析过程中,DNS查询中的每次迭代引用都容易受到恶意攻击。比如网络攻击者可以通过中间人攻击等手段窃取DNS请求,用恶意记录和正确的ID字段构建一个恶意的响应数据包,并在合法响应数据到达之前竞相发送恶意响应,如果恶意记录的生存响应延迟(TTL)再被设置为一个非常高的数值,那么受害者将在很长一段响应时间内缓存这些恶意域名解析记录,导致终端客户被引导访问恶意网站。一个典型的案例是DNS Kaminsky攻击[3],它实际上是利用截获DNS消息中的查询ID,采用中间人攻击手段,使DNS解析器接受伪装的DNS响应。
为了有效解决DNS安全防护的问题,互联网工程任务组(IETF)开发了DNS安全扩展(以下简称DNSSEC)协议[4],DNSSEC采用非对称加密机制来验证记录完整性和真实性。为实现DNS有效防护,每个区域必须至少提供下列三种记录类型:(1)DNSKEY记录。该记录是公钥,每个Zone区用相应的私钥签署DNS记录,解析器使用DNSKEY来验证这些签名。Zone区通常创建两个DNSKEY记录(KSK和ZSK)来签署DNS记录:KSK的私钥用于签署DNSKEY记录,ZSK的私钥用于签署所有其他记录;(2)RRSIG(资源记录签名)记录。它是使用DNSKEY对应私钥签署其他记录的加密签名;(3)DS记录。它是由域名注册商上传到父区的DNSKEY的哈希值。为了确保完整性,DS记录也需要由父区签署(在DS记录的RRSIG中)。
因此,只有当从根区到叶区都有有效的DS记录时,DNSSEC才能正常运行,从而建立起一个信任链。图1是一个三层信任链的模型(子区、父区和根区各拥有一个ZSK和一个KSK)。
图1 基于信任链的DNS来源与完整性认证
因此,如果一个解析器被配置了像信任密钥一样的根区的KSK,它将能够通过建立图1这样的信任链来检查子区域的信息,实现DNS数据的来源、完整性验证,确保了DNS消息来源的权威性和可靠性。
尽管DNSSEC设计了理论上近乎完美的DNS保护机制,它从1995年就开始发展,第一个DNSSEC已经被开发和标准化了二十多年,但其在互联网上的实际部署率却不理想,只有47%的ccTLDs(国家代码顶级域)签署了DNSSEC。[5]经过分析,笔者认为主要存在以下四个方面的问题:
在DNSSEC中,DNS响应的完整性是通过使用公钥加密技术和数字签名来保证的,因此权威服务器必须定期正确地维护(更换)私钥和公钥对。数量庞大的各层级权威服务器显然需要高昂的维护成本。
在一般情况下,DNSSEC验证是在DNS全解析器上进行的,以便实际使用其缓存功能,因此DNSSEC验证过程中复杂的密钥计算也将大大增加DNS全解析器的工作负荷,从而导致域名解析性能的下降。
由于DNSSEC验证主要是在DNS全解析器上完成的,所以终端用户电脑上的存根解析器并没有被DNSSEC的安全名称解析所覆盖。在许多网络环境的主机中,因为终端用户操作系统的存根DNS解析器不支持DNSSEC,所以终端用户不能使用DNSSEC,存在安全短板。
由于采用签名认证机制,每个记录都要有一个签名记录RRSIG记录, DNSSEC需要传输这些非标准的DNS数据包,DNSKEY 和 RRSIG 记录实际响应包长度均比标准 UDP 的DNS数据包(通常小于512 B)大很多。根据统计,Zone区数据一般要扩大10倍以上(具体实际数值与采用的密钥长度关联),相应的网络流量肯定也要相应放大,过强的流量放大效应将更容易造成针对DNS的大规模DDoS 攻击[3]。
针对DNSSEC部署存在的上述缺陷,如果尝试在解析器之间使用一个不同的、外部的信任源(比如PKI),是否有可能在不依靠验证证书链的情况下保证DNS域名解析服务事实上的安全性?笔者尝试采用基于PKI 的外部CA证书的思路来解决DNS信任链的问题。
公钥基础设施(PKI)是一个管理数字证书和公钥加密的系统,PKI基于数字证书,它由证书机构(CA)颁发[6]。例如在PKI中最常用的X.509证书包括代表域名的通用名称,一个公钥。CA有它的根证书,根证书签署次级证书。用户可以用父级证书验证子级证书,也同样可以建立一个信任链[7]。
超文本安全协议(HTTPS)就是使用数字证书进行安全数据传输的一个例子。由一个特定的CA颁发的证书将给定的域名与证书受让人、申请该证书的实体、证书有效期等信息结合起来,CA用于向浏览器证明网络服务器代表了客户所要求的域名,在成功认证后,浏览器和网络服务器之间会建立一个TLS连接。随着TLS连接的建立,浏览器和网络服务器之间的通信将被加密,并防止任何第三方的窃听。通过HTTPS,网络服务器使用数字证书来加密他们与客户之间的所有通信。根据谷歌统计[7],目前全世界大约96%的网页都是通过HTTPS加载的。与DNSSEC相比,数字证书的部署率非常高。
由于DNSSEC的层次结构,在父域部署DNSSEC之前,子域无法部署DNSSEC。此外,其上传DS记录的过程中,一些子域名服务器忘记上传DS记录或上传错误,大约30%的签名域名在其父区没有DS记录。而本文所提出解决方案的关键思路是验证记录而不是上传DS记录,ProxyDNS使用基于PKI的CA数字证书,而不是信任链。在DNSSEC中,名称服务器中的记录是由ZSK签署的。这些ZSK再由KSK签署。KSK以DS记录的形式上传到父域。这就形成了一个从记录到根KSK的信任链。
而在ProxyDNS中,名称服务器用他们自己的证书签署记录,这些证书由基于PKI的CA颁发。ProxyDNS不需要向父区上传任何记录。
笔者使用CA证书来验证数据的完整性,而不是使用DS记录。每个权威名称服务器用CA颁发给它的数字证书来签署他们的记录。每个记录的信任锚移到他们的CA上,消除了DNSSEC中父域、子域之间的依赖性。在这个场景中,记录验证过程如下:(1)权威名称服务器拥有由CA颁发的数字证书,并用证书的私钥生成记录的签名;(2)当ProxyDNS代理(或解析器)收到查询时,代理客户请求DNS查询记录、生成签名和证书。在证书链被验证后,公钥被从证书中提取出来并被缓存,如果验证有效,则将记录发送给客户。
ProxyDNS作为一个代理程序在终端用户机器上运行。客户端不知道该代理的存在,并像往常一样向DNS解析器发送请求。代理程序会截获该查询请求,并将其发送给DNS解析器。ProxyDNS代理验证了这些记录,然后将查询的答案再发送给终端用户。客户端代理的实现也满足了向后的兼容性,保证了端到端的DNS安全性。
由CA颁发给权威名称服务器的证书被用于DNS记录验证,由于签名是通过使用证书的私钥产生的,代理通过使用配对的公钥来验证DNS记录,为了让代理接触到证书,该证书必须作为记录存储在权威名称服务器中。根据RFC4398,数字证书可以被储存为一条记录,这里取名为CERT记录,其结构主要包括:主机名、记录类型、证书类型、密钥标签、用于生成证书的算法类型、TTL、以及用Base64编码的证书。
在生成签名的情况下,使用TXT记录。在ProxyDNS中,为每个RRset生成签名,类似于DNSSEC的RRSIG签名[7]。例如,为A记录RRset创建一个签名,首先使用SHA256函数创建一个摘要。如果有多条A记录,每条记录被连接成一个字符串,中间用冒号分隔,然后生成一个摘要。有了这个摘要,采用OpenSSL证书的私钥为A记录创建一个签名。最后,签名用Base64进行编码,作为TXT记录上传。由于TXT记录有255个字符的限制[4],签名被分成200个字节,并与包含签名所属记录信息的头部一并存贮。
当收到来自客户端的查询时,代理按图2顺序操作。
图2 ProxyDNS记录验证的流程
第一步:创建一个查询,获取记录(这里是TXT记录)的签名。在获取TXT记录后,代理找到带有ProxyDNS头的字符串,然后解析TXT记录并重建签名,最后代理得到了记录的签名;第二步:检查缓存。如果记录的公钥被缓存了,代理就会加载缓存的公钥,如果没有缓存,则创建一个CERT记录的查询来获取CERT记录。当CERT记录被获取时,它的证书链被验证,并且公钥被从CERT记录的证书中提取,公钥被缓存在代理处。第三步:验证该记录。利用重建的签名和证书中的公钥,对记录进行验证。
按照这个ProxyDNS代理的设计思路,笔者使用C语言开发了一个原型程序,搭建了一个仿真测试环境。
假设验证“www. yzpc-dns-test.com”的A记录。客户端像往常一样发送DNS查询。客户端运行“dig www. yzpc-dns-test.com/A”命令。这个请求被转发给代理,代理向本地解析器请求一个相应的A记录。然后返回“www. yzpc-dns-test.com”包含其 IP 地址的 A 记录。
在获取A记录后,对TXT记录的查询被发送到解析器。检查已经收到的TXT记录,找到带有ProxyDNS头的字符串。例如,A记录的头部看起来像 “ProxyDNS-Base64-A-1”“ProxyDNS-Base64-A-2”“ProxyDNS-Base64-A-n”等。对头文件进行解析,重建签名,并通过Base64进行解码,最后,使用SHA256算法生成IP地址的摘要。
在重构签名后,检查缓存。由于最初没有任何缓存,缓存没有命中,相应记录的CERT记录被ProxyDNS查询到,该证书位于该记录的权威DNS服务器上。如果“www. yzpc-dns-test.com”的权威名称服务器是“ns. yzpc-dns-test.com”,就会向“ns. yzpc-dns-test.com”生成新的CERT记录查询。当从本地解析器获取“ns. yzpc-dns-test.com”的CERT记录时,首先对证书链进行验证。假设根证书和中间证书都存储在客户端,证书链将使用OpenSSL进行验证。当证书链被确认后,ProxyDNS检查证书的公共名称是否为“ns. yzpc-dns-test.com”。这是因为需要检查该证书是否是用于记录验证的正确证书。当证书的验证完成后,使用OpenSSL从证书中提取公钥并进行缓存,公钥被缓存为一个文件。
有了摘要、公钥和签名,记录就可以用OpenSSL进行验证。当验证完成后,返回客户端的响应。
在数据中心私有云上安装一个DNS解析器虚拟机,使用Bind9来刷新缓存。从域名服务商注册“yzpc-dns-test.com”域名用于权威服务器的记录管理。“yzpc-dns-test.com”区的权威名称服务器也使用Bind9安装在数据中心私有云虚拟机上。
从响应延迟、存储消耗开销的角度对ProxyDNS和DNSSEC进行比较。
为了比较DNSSEC和ProxyDNS的响应延迟开销,使用“dig”命令测量解析响应延迟,客户端收到查询响应的响应延迟。使用“dig”命令发送了对A记录的查询,在正常情况下,它是在关闭ProxyDNS和DNSSEC功能的情况下测量的。在开启DNSSEC的场景下,它是在关闭ProxyDNS功能的场景下设置了DNSSEC后测量的。在启用ProxyDNS的场景下,它是在关闭DNSSEC并只使用ProxyDNS的功能时测量的。响应延迟数据分别是三种场景各测量20次的结果平均值(见表1)。
表1 DNS、DNSSEC和ProxyDNS响应时间比较 单位:s
在没有任何安全扩展的情况下,A记录的解析响应延迟约为0.21s。当部署了DNSSEC后,解析响应延迟增加到约0.46s,几乎慢了2倍多。在采用ProxyDNS的情况下,解析响应延迟约为0.28s,几乎比DNSSEC快40%。
为了比较DNSSEC和ProxyDNS中父区的存储消耗开销,可查看每个系统中使用的记录(见表2)。RRSIG和TXT记录的比较被排除在外,因为DNSSEC和ProxyDNS都是用相同的方法来生成和管理签名。
表2 DNSSEC和ProxyDNS存储消耗比较
当使用DNSSEC时,会生成一个DNSKEY记录,并以DS记录的形式上传到父域。在一个权威服务器中,DNSKEY的开销不大,但在必须存储DS记录的父域(如TLD)中,存储消耗开销很大。根据statdns[8],在“.com”域中大约有440万条DS记录被上传。假设所有这些DS记录都是用SHA-256算法上传的,计算下来相当于300MiB左右。而在ProxyDNS中,上传DS记录的过程是不需要的,所有的DS记录都可以用一个CERT记录代替。一个CERT记录仅占2KiB,与使用DS记录相比,可以将存储消耗开销降低到DNSSEC方案的0.0008%。
文章分析了目前作为DNS防护主要解决方案DNSSEC部署率过低的原因,研究得出DNSSEC的分层结构中证书链验证开销过大是DNSSEC部署率偏低的一个主要因素。提出了通过减少证书链签名验证的数量,将大部分的计算、带宽、存储开销成本尽可能降低的思路来克服DNSSEC目前实际部署存在的困难。笔者开发的基于PKI的ProxyDNS原型程序,在没有分层证书验证的情况下提供了DNS查询数据的完整性和真实性验证,使用PKI公钥基础设施中的CA数字证书进行数据验证,由于不需要向上层域上传记录,因此可以消除证书链的层次结构。最后测量ProxyDNS响应延迟和数据存储开销。仿真测试结果表明:与DNSSEC方案相比,可以用很少的额外开销来维持DNSSEC几乎同等的DNS安全水平,特别在性能方面比DNSSEC有明显的提升,可以作为实现DNS安全的一个轻量化解决方案。