以前对于DNS相关的技术仅仅是略懂皮毛,没有进行全面和深入的了解。最近几年由于各方面的原因,主动被动的接触了不少,包括:
- 家庭组网和改造:尤其是近两年国内IPv6的普及;
- 公司兼职网管:负责处理公司的网络问题和VPN问题;
- VPS:也就是本博客VPS的配置;
- 项目、任务:内网渗透、应急响应等;
- 越来越糟糕的网络环境。
其中,影响最大的是DoH、DoT、DNSCrypt等安全DNS协议的出现,同时越来越多的厂商开始部署,主流操作系统也开始逐渐支持。这是一个好的开始,至少在保护个人隐私方面有了更多的选择。同时,我也开始在个人电脑、家庭、公司网络(小范围)中部署、测试安全DNS,为了做出更好的选择,在这过程中学习了很多DNS相关的知识,趁着最近因为疫情仍未复工,整理一些以前的笔记。
今天先从DNSSEC说起。作为一项互联网的基础设施,DNS协议十分重要但过于单纯,也正因如此,DNSSEC很早就被提出,是一种通过扩展DNS协议实现认证的机制,其核心原理是使用数字签名机制和信任链来确保DNS的完整性。通过DNSSEC机制,DNS Resolver或者终端用户可以确保DNS查询到的结果是否可信,即是否为权威域名服务器返回,某种层面上增加了DNS的安全性。
DNSSEC简介
DNSSEC通过扩展资源记录(Resource records)实现认证,增加的几个主要类型如下:
- DNSKEY:用于校验RRSIG的公钥信息
- DS:delegation signer,保存DNSKEY的哈希信息
- RRSIG:resource record signature,RRset的数字签名
和A记录、TXT记录一样, DNSKEY、DS记录均能够通过DNS查询获取:
➜ ~ dig @8.8.4.4 +dnssec thecjw.me. DNSKEY ;; ANSWER SECTION: thecjw.me. 3599 IN DNSKEY 257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+ KkxLbxILfDLUT0rAK9iUzy1L53eKGQ== thecjw.me. 3599 IN DNSKEY 256 3 13 oJMRESz5E4gYzS/q6XDrvU1qMPYIjCWzJaOau8XNEZeqCYKD5ar0IRd8 KqXXFJkqmVfRvMGPmM1x8fGAa2XhSA== thecjw.me. 3599 IN RRSIG DNSKEY 13 2 3600 20200313043621 20200113043621 2371 thecjw.me. bWMvyG5jcY0dqCN9pi323O3cUcCODs3lyOZRpT54Tkx6Oz1Zvvp3QLa1 nppPBW9SVpiOodzQbYTF3zCTZZJH0g==
可以看到,正确配置了DNSSEC的服务器在响应中均包含了RRSIG字段,该字段内容包含了域名、时间、签名、算法等。不考虑信任链的情况下,DNS Resolver首先通过DNSKEY记录获取公钥,之后便可通过DNSKEY中记录的Zone Signing Key(公钥)来验证之后的DNS请求是否来自于权威域名服务器。
因为最终完成DNS查询的均是权威域名服务器,所以上述所有值都来自于权威域名服务器。比如本博客使用CloudFlare作为CDN,则权威服务器便是CloudFlare的服务器:
➜ ~ dig @8.8.4.4 +dnssec thecjw.me. NS ;; ANSWER SECTION: thecjw.me. 21599 IN NS asa.ns.cloudflare.com. thecjw.me. 21599 IN NS brett.ns.cloudflare.com.
在开启了DNSSEC后,CloudFlare还负责使用私钥对RRset(资源记录)进行签名,并将结果放入RRSIG记录中,同时将签名对应的公钥放在DNSKEY中,以便于DNS Resolver进行检查。
仅仅通过上述机制,只能部分证明RRset是否完整(功能等同于哈希),因为公钥和签名都来自不可信地方,攻击者可以通过篡改DNSKEY和RRSIG绕过校验。基于这种最基本的校验逻辑,DNSSEC进一步使用DS和Key-Signing Key机制,构建了DNSSEC的信任链。
DNSSEC信任链
为了实现信任链,DNSSEC中的签名信息被分为了Zone-Signing Key和Key-Signing Key。
Zone-Signing Key(ZSK),顾名思义,用于对Zone内所有的资源记录进行签名,由权威认证服务器生成、签名。权威认证服务器在每次DNS查询的时候,都会使用ZSK对查询结果(资源记录)进行数字签名,并将数字签名放在RRSIG记录中。ZSK可以通过DNSKEY记录获取。
为了防止ZSK被修改,DNSSEC还使用了一对叫做Key-Signing Key(KSK)的公私钥对。DNSKEY记录的RRSIG会使用KSK进行签名,并将结果放在DNSKEY的RRSIG记录中。在DNSKEY记录中,会同时包含ZSK和KSK。所以,在一个zone中,除DNSKEY记录外,其他的记录均由ZSK签名。
即便如此,还是无法构成信任链。此时就需要DS记录将当前的Zone和TLD的Zone Signning Key进行连接,构成信任链。DS记录中包含了KSK的哈希值,DNS Resolver可以通过DS记录确定KSK的完整性,进而确定DNSKEY记录的完整性,最后确定当前zone其他DNS记录的完整性。注意到,DS记录也包含RRSIG记录,而该记录的签名由上一级的域名服务器使用私钥生成,该私钥对应的公钥在上一级DNS服务器的DNSKEY记录中。上一级服务器的DNSKEY校验过程和本级的类似,如此往复,直到根服务器,也就是DNSSEC Root。DNSSEC信任根的信息可以在IANA DNSSEC的网站找到。
所以,DNSSEC通过DS记录和使用上一级Zone-Signing Key对Zone的KSK的哈希进行签名的方法,构建了自身的信任链。这种体系相对简单,不涉及到密钥的管理等问题,而且配置相对简单。以在CloudFlare开启为例,开启DNSSEC时,CloudFlare会为当前的Zone生成特定的KSK和ZSK,同时提供KSK的哈希(包括算法),也就是DS记录的信息;将DS记录的信息提交到域名提供商(当前的域名是me.)即可。
整个DNSSEC的过程图示如下:
使用dig校验
为了更好的理解整个过程,下面使用dig进行从下往上的校验流程分析。
除了通过手工的方式,以下工具还可以更清晰的理解、校验DNSSEC。使用+sigchase参数,可以让dig输出DNSSEC校验的过程,该参数需要指定信任的根证书,实践中,应该通过安全的方式获取,这里为了方便,直接使用dig查询根节点的DNSKEY并存储,命令如下:
dig @8.8.8.8 . DNSKEY | grep -Ev '^($|;)' > root.keys
接下来,可以直接使用dig进行DNSSEC的校验:
dig @8.8.8.8 +sigchase +trusted-key=./root.keys thecjw.me. A
结果比较长,需要分步骤来看。首先dig输出了A记录的查询结果和对应的RRSIG:
从RRSIG中可以看到,当前的DNS记录使用了KeyTag为34505的ZSK进行签名,而密钥需要通过DNSKEY记录获取:
上述记录即查询DNSKEY的结果,该记录也包含了RRSIG,与A记录不同,使用了KeyTag为2371的key进行签名,注意DNSKEY记录的签名由KSK完成。通过+multiline参数,可以看到key的信息:
和预期的一样,2371为KSK,34505为ZSK。目前,还需要DS记录来验证KSK是否完整,回到dig的输出:
上图的dig日志中,首先获取了DS记录。这里需要注意的是,DS记录同样进行了签名,通过RRSIG记录,可以看到,负责对DS签名的KeyTag为39077,不属于本Zone的ZSK和KSK,而是属于上一层TLD的ZSK,之后的校验就要寻找KeyTag为39077的公钥,用于DNSSEC校验信任链。之后的log为使用DNSKEY、DS进行校验的过程输出。首先通过DNSKEY中的ZSK校验A记录是否完整,然后通过DS校验KSK是否完整,如果KSK完整,则DNSKEY完整,进而被ZSK签名的A记录完整。此时就该向上继续了,现在要做的是获取me.的DNSKEY:
可以看到,me.的DNSKEY记录中包含了4个KEY,继续使用+multiline参数查看具体的KeyTag:
由上图可知,me.的DNSKEY如下:
- 39077:ZSK
- 46829:ZSK
- 2569:KSK
- 53233:KSK
其中,39077就是对本域名进行签名的公钥。而DNSKEY记录中的RRSIG信息显示,该DNSKEY分别被两个KSK签名。但39077这个ZSK也对DNSKEY签名了,这里没搞懂。有了DNSKEY之后,需要me.的DS记录,用于校验KSK的有效性:
具体过程不再赘述,和之前的校验过程类似,因为包含了两个KSK,所以DS记录中有两条,验证的过程中,只会对和校验链相关的进行验证。同时需要注意,该DS记录由KeyTag为33853的Key进行签名,而该签名来自于根证书。所以需要继续向上进行校验:
再一次的,查看根域名服务器的DNSKEY信息:
如预期所料,根域名服务器的DNSKEY中包含了一个ZSK,正是负责给me顶级域名服务器DS记录签名 33853。注意到,该DNSKEY记录使用了20326进行签名,而顶级服务器没有DS记录,那应该如何校验呢?实际上这里已经是根证书的地方,20326的证书即DNSSEC的可信根证书,可以通过IANA的网站获取。20326的哈希如下:
<TrustAnchor id="380DC50D-484E-40D0-A3AE-68F2B18F61C7" source="http://data.iana.org/root-anchors/root-anchors.xml"> <Zone>.</Zone> <KeyDigest id="Kjqmt7v" validFrom="2010-07-15T00:00:00+00:00" validUntil="2019-01-11T00:00:00+00:00"> <KeyTag>19036</KeyTag> <Algorithm>8</Algorithm> <DigestType>2</DigestType> <Digest> 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5 </Digest> </KeyDigest> <KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00"> <KeyTag>20326</KeyTag> <Algorithm>8</Algorithm> <DigestType>2</DigestType> <Digest> E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D </Digest> </KeyDigest> </TrustAnchor>
如果一个DNS请求的数据能够通过上述校验,则证明在DNS响应的过程中,DNS的结果没有被篡改。
其他工具
DNSSEC Analyzer/DNSViz
DNSSEC Analyzer是VeriSign推出的一个在线DNSSEC检测工具,可以快速的对DNSSEC进行检测,同时输出较为关键的Log。如果DNSSEC配置正常,效果如下:
图中展示了各个Zone中参与运算的Key和校验的过程。从运行过程来看,DNSSEC Analyzer是通过自顶向下的方式进行校验。另外两个DNSSEC failed的效果如下:
VeriSign的另外一个Dnsviz工具则以可视化的方式展示了整个DNSSEC校验的过程,并且还在Github开源了。
dnspython
使用python的好处就是可以看到一些dig中需要解析的数据,同时进行一些其他的测试。dnspython库可以完成DNSSEC的校验,但校验信任链需要自行完成。另外,dnspython进行DNSSEC校验依赖于pycryptodome和ecdsa两个库,由于在extras_require中,需要自行安装,否则会校验失败:
'tests_require': ['typing ; python_version<"3.5"'], 'extras_require': { 'IDNA': ['idna>=2.1'], 'DNSSEC': ['pycryptodome', 'ecdsa>=0.13'], },
进行校验的演示代码如下:
target = "thecjw.me." default_dns_server = "8.8.8.8" query = dns.message.make_query(target, dns.rdatatype.DNSKEY) response = dns.query.udp(query, default_dns_server, 3) dnskeys = response.answer[0] query = dns.message.make_query(target, dns.rdatatype.A, want_dnssec=True) response = dns.query.udp(query, default_dns_server, timeout=5) answer = response.answer if answer[0].rdtype == dns.rdatatype.RRSIG: rrsig, rrset = answer elif answer[1].rdtype == dns.rdatatype.RRSIG: rrset, rrsig = answer keys = {dns.name.from_text(target): dnskeys} dns.dnssec.validate(rrset, rrsig, keys)
注意,这段代码并不严谨,完整的代码请参考electrum工程中dnssec.py的实现。
总结
DNSSEC通过数字签名实现了一种认证机制,确保了DNS Resolver有方法校验查询到的DNS记录是否来自可信来源,对于中间人攻击有一定的效果。但DNSSEC的缺点也比较明显,一方面是DNSSEC的普及度并不高,对于DNS Resolver而言必须做兼容处理,而攻击者完全可以对DNSSEC相关的数据继续剥离,欺骗DNS Resolver目标没有开启DNSSEC,在国内这种情况十分常见。
同时,开启DNSSEC的网站数目相对较少,测试了一些大厂的域名,基本都没有设置。也正因为如此,大多数公共DNS基本没有对DNSSEC进行处理,测试中发现,在使用114DNS的情况下,http://dnssec.fail/依旧可以访问:
使用dig和GoogleDNS进行对比的结果如下:
➜ ~ dig @8.8.4.4 +dnssec dnssec.fail ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags: do; udp: 512 ;; QUESTION SECTION: ;dnssec.fail. IN A ➜ ~ dig @114.114.114.114 +dnssec dnssec.fail ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 512 ;; QUESTION SECTION: ;dnssec.fail. IN A ;; ANSWER SECTION: dnssec.fail. 2822 IN A 194.63.248.47
最后,DNSSEC并没有对DNS记录进行加密,还是存在被追踪、监视的风险。所以,DNSSEC正如其名,只是一个安全的扩展,并不能真正的解决DNS的安全和终端用户的安全需求。目前DoT、DoH、DNSCrypt配合可信DNS Resolver的方式,才真正的解决了DNS的安全问题,具体细节以后再写。
参考
- DNSSEC原理、配置与部署, http://www.edu.cn/xxh/ji_shu_ju_le_bu/wlaq/gjff/sqt/201106/t20110627_640934.shtml
- How to test and validate DNSSEC using dig command line, https://www.cyberciti.biz/faq/unix-linux-test-and-validate-dnssec-using-dig-command-line/
- dnssec-analyzer, https://dnssec-analyzer.verisignlabs.com
- DNSSEC verification with dig, https://backreference.org/2010/11/17/dnssec-verification-with-dig/
- How DNSSEC Works, https://medium.com/iocscan/how-dnssec-works-9c652257be0