DNSpooq系列漏洞分析与复现

发布时间 2021-02-01

前言


近期,以色列安全咨询企业JSOF在最新报告中披露了七个 DNSmasq 漏洞(统称 DNSpooq),并指出攻击者借此感染了数以百万计的设备。DNSmasq 是一套流行的开源 DNS 转发软件,能够为运行该软件的网络设备添加 DNS 缓存和 DHCP 服务器功能,广泛用于各种小型局域网络。受 DNSpooq 影响的设备不仅可能遭遇 DNS 缓存中毒,还可被用于远程代码执行、拒绝服务(DoS)攻击。目前受影响的厂商包括但不限于 Android / Google、康卡斯特、思科、红帽、Netgear、高通、Linksys、IBM、D-Link以及 Ubiquiti 。根据shodan显示,有超100万台应用DNSmasq的设备暴露在公网,可能受影响的设备不计其数。


其中, CVE-2020-25684、CVE-2020-25685 和 CVE-2020-25686 这三个漏洞,可能导致 DNS 服务遭遇缓存中毒攻击。另外四个漏洞为 CVE-2020-25687、CVE-2020-25683、CVE-2020-25682 和 CVE-2020-25681 ,均为缓冲区溢出漏洞。黑客或可在配置了 DNSmasq 的网络设备上,利用这些漏洞远程执行任意代码。


DNS协议简介


DNS的请求和响应的基本单位是DNS报文(Message)。请求和响应的DNS报文结构是完全相同的,每个报文都由以下五段(Section)构成:


1.png


DNS Header是每个DNS报文都必须拥有的一部分,它的长度固定为12个字节。Question部分存放的是向服务器查询的域名数据,一般情况下它只有一条Entry。每个Entry的格式是相同的,如下所示:


2.png


QNAME:由labels序列构成的域名。QNAME的格式使用DNS标准名称表示法。这个字段是变长的,因此有可能出现奇数个字节,但不进行补齐。DNS使用一种标准格式对域名进行编码。它由一系列的label(和域名中用.分割的label不同)构成。每个label首字节的高两位用于表示label的类型。RFC1035中分配了四个里面的两个,分别是:00表示的普通label,11(0xC0)表示的压缩label。


Answer,Authority和Additional三个段的格式是完全相同的,都是由零至多条Resource Record(资源记录)构成。这些资源记录因为不同的用途而被分开存放。Answer对应查询请求中的Question,Question中的请求查询结果会在Answer中给出,如果一个响应报文的Answer为空,说明这次查询没有直接获得结果。


RR(ResourceRecord)资源记录是DNS系统中非常重要的一部分,它拥有一个变长的结构,具体格式如下:


3.png


● NAME:它指定该条记录对应的是哪个域名,格式使用DNS标准名称表示法

● TYPE:资源记录的类型。

● CLASS:对应Question的QCLASS,指定请求的类型,常用值为IN,值为0x001。

● TTL(Time To Live)资源的有效期:表示你可以将该条RR缓存TLL秒,TTL为0表示该RR不能被缓存。TTL是一个4字节有符号数,但是只使用它大于等于0的部分。

● RDLENGTH:一个两字节非负整数,用于指定RDATA部分的长度(字节数)。

● RDATA:表示一个长度和结构都可变的字段,它的具体结构取决于TYPE字段指定的资源类型。

   DNS常见资源记录类型有NS记录、A记录、CNAME记录。

● NS记录

NS记录用于指定某个域的权威DNS。比如在com的DNS里,记录着http://baidu.com这个域的DNS,大概如下:

baidu.com.  NS ns1.baidu.com. 

baidu.com.  NS ns2.baidu.com. 

baidu.com.  NS ns3.baidu.com.


这三条记录,就是说http://ns1.baidu.com、http://ns2.baidu.com、http://ns3.baidu.com(以下简称ns1、ns2、ns3)都是http://baidu.com域的权威DNS,询问任意其中一个都可以。


当然,在com的权威DNS里,还会记录ns1~ns3这几个http://baidu.com权威DNS的IP,会一并返回给问询者,以便问询者直接用IP联系ns1~ns3。


● A记录


A记录就是最经典的域名和IP的对应,在http://ns1.baidu.com里面,记录着百度公司各产品的域名和IP的对应关系,每一个这样的记录,就是一个A记录,比如下面的3个A记录:


image.baidu.com   A    1.2.3.4 

wenku.baidu.com   A    5.6.7.8 

tieba.baidu.com     A    9.10.11.12


如果用户询问http://ns1.baidu.com:“http://wenku.baidu.com的IP是多少?”,ns1就会找到对应的A记录或者CNAME记录并返回。


● CNAME记录


CNAME记录也称别名记录,允许将多个记录映射到同一台计算机上。比如,在ns1中,并没有http://www.baidu.com的A记录,而是一个CNAME记录:


www.baidu.com  CNAME  www.a.shifen.com


也就是告诉用户,http://www.baidu.com的别名是http://www.a.shifen.com,可以直接请求解析http://www.a.shifen.com。


DNS缓存攻击


当访问www.baidu.com时,域名解析的大致流程如下图所示。


4.png


DNS缓存中毒是一种比较经典的攻击方式,如果攻击者可以成功执行,就会在DNS缓存服务器上留下一个有害的条目,使得用户访问正常网站的请求重定向到被攻击者控制的恶意网站。


DNSpooq系列缓存中毒漏洞的简单攻击流程图如下图所示:


5.png


(1)用户发送浏览淘宝的请求给DNS转发器,希望得到对应的IP。

(2)DNS转发器没有此域名的缓存,所以将请求转发给上游DNS服务器。

(3)在得到上游DNS服务器回复前,攻击者发送一个伪造的回复,将淘宝域名与一个恶意IP相对应。

(4)DNS转发器接受了这个伪造的回复,并发送给用户,于是用户请求访问的淘宝被重定向到了攻击者操纵的恶意网站。


这个DNS转发器应用场景很广泛,比如个人开的热点,机场、宾馆里的公共网络等,一旦攻击成功,则影响使用这些网络的所有人。


在DNS Header中有一个16-bit的区域叫TXID(transaction ID),用于将查询包和回复包匹配。在过去,TXID是防御DNS缓存中毒的重要手段。但是在2008年,安全研究员Dan Kaminsky证明16-bit的TXID是远远不够的,后来又增加了端口随机化,所以这个时候想伪造回复包,不仅需要猜对TXID,还需要猜对端口,一共32位的随机值,此外还需要知道源IP和目的IP。


DNS安全扩展


到了21世纪,DNS安全扩展正在被慢慢应用。DNS安全扩展是目前为了解决DNS欺骗和缓存污染问题而设计的一种安全机制。DNSSEC依靠数字签名来保证DNS应答报文的真实性和完整性。简单来说,权威服务器使用私钥对资源记录进行签名,递归服务器利用权威服务器的公钥对应答报文进行验证。如果验证失败,则说明这一报文可能是有问题的。


为了实现资源记录的签名和验证,DNSSEC增加了四种类型的资源记录:RRSIG(Resource Record Signature)、DNSKEY(DNS Public Key)、DS(Delegation Signer)、NSEC(Next Secure)。


例如我们执行命令行:dig @8.8.8.8 paypal.com,得到的DNS查询结果如下所示:


6.png


红框中为应答部分,这是未开启DNSSEC的情况下的。我们执行命令行:dig+dnssec @8.8.8.8 paypal.com,得到的DNS查询结果如下所示:


7.png


蓝框中便是RRSIG资源记录存储,该资源记录存储的是对资源记录集合(RRSets)的数字签名。


Dnsmasq缓存中毒漏洞


以下三个漏洞,组合起来用可以降低伪造回复包的熵值。


CVE-2020-25684


DNSmasq本身限制了转发给上游服务器查询包的数量,通常最大是150条。用户可以自己设定这个值。转发查询使用的是frec(forwardrecord)结构。每个frec都和TXID相关联。当回复被接受或经过一定时间,这个frecs就会被删除。


通常情况下,用于转发查询的socket数量被限制在64个。每个用于转发的socket和一个随机的端口绑定。

理论上,查询包中TXID和源端口加起来会有32-bit的熵。但是实际上,这个熵要更少一些。因为dnsmasq在同一个端口会多路复用多个TXID,而没有将每个TXID和每个端口设置为一一对应的关系,如下图所示。结果就是,攻击者只需要猜中64个端口中的一个端口还有正确的TXID就可以了,而不用猜中某个特定的端口和特定的TXID。所以这导致实际上只有26位熵值。


8.png


● CVE-2020-25685


如果要对DNS转发器进行投毒,除了需要猜对正确的TXID和源端口,攻击者发送伪造的回复还需要匹配已开放的frecs。如果想让frec匹配,那么TXID和问题区都要匹配,换句话说,回复的内容是之前询问过的。


dnsmasq只存放问题区的哈希值,而不是把整个语句存下来。当整个查询提交的时候,这个哈希值会被保存。


如果dnsmasq没有编译DNSSEC支持,那么他默认使用CRC32作为哈希算法。问题就在于CRC32从密码学角度并不是一个安全的算法。可以很轻松的使用类似SMT solver等工具进行CRC32碰撞,这里原理不做过多介绍。


所以基于这一特性,攻击者可以生成多个查询,每一个查询的CRC32的值都相同,不过查询的是不同的域名,而这些域名最好是不存在的,即没有被缓存的。然后攻击者可以发送一个具有相同CRC32值的伪造的回复。


如下图所示,攻击者控制一台客户端对多个域名发起问询,每一个CRC32的值都是相同的,然后在递归DNS服务器回复之前,回复一个具有相同CRC32值的域名或IP,攻击即有可能成功。


9.png


● CVE-2020-25686


dnsmasq的另一个问题就是在同一个域名被查询请求时会粗暴的创建多个frecs。随后会转发所有的请求,如果成功的匹配其中的任意一个,就计入缓存。这个问题导致就算dnsmasq使用安全的哈希算法,也可能成功的实施攻击。


通过以上三个漏洞,导致攻击者伪造恶意回复包的成功率大大提高,后面还需要利用dnsmasq没有对回复包做验证的特性进行攻击。


一般情况下,在递归服务器上会对回复包做一些验证机制,例如bailiwicks。但是在配置dnsmasq的设备上并没有做任何验证,所以可以在用户请求www.example.com的时候,攻击者可以发送如下回复:


www.example.com  CNAME  www.bank.com

www.bank.com           A         6.6.6.6


然后这条记录的缓存就会被插入到dnsmasq的设备中。前文介绍过CNAME,所以当用户想访问www.bank.com的时候,会被重定向到被攻击者控制的IP为6.6.6.6的服务器。而配置了类似bailiwicks的设备,会去找权威服务器询问www.bank.com的IP。


Dnsmasq缓冲区溢出漏洞



● CVE-2020-25681


以下名称以规范的DNS名称顺序排序。最重要的标签是“example”。在此级别上,“example”将首先排序,然后是以“a.example”结尾的名称,然后是以“z.example”结尾的名称。每个级别中的名称以相同的方式排序。如下图所示。


10.png


CVE-2020-25681漏洞位于dnssec.c文件的sort_rrset()函数中,该函数负责按照DNSSEC验证过程的要求采用冒泡排序算法将给定的资源记录集合(RRSets)排序为规范顺序。该函数定义如下:


11.png


它接受了响应数据包(header)以及数据包长度(plen)。rrset是指向资源记录集合中RR数组的指针,而rrsetidx是集合中的RR数,rr_desc是指向与RRset关联的RR类型的描述符的指针。最后,有两个缓冲区buff1和buff2,它们用作排序例程的工作区缓冲区。这两个缓冲区在程序开始时都是相对分配的,它们是daemon> workspacename和daemon-> keyname。当dnsmasq开启DNSSEC时,将会分配这两个缓冲区。


12.png


MAXDNAME大小为1025,所以workspacename和keyname的大小2050,也是该漏洞发生溢出的缓冲区。


首先启动dnsmasq,并设置参数为:

-p 53535 --no-daemon --log-queries -S127.0.0.2 --no-hosts --no-resolv -d -q --dnssec--trust-anchor=.,20326,8,2,E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D,构造完恶意DNS查询响应包,使用命令行:dig NS+dnssec @localhost -p 53535 .,命中sort_rrset()函数断点后如下图所示:


13.png


在构造资源记录集合(RRSets)时,必须保证记录个数大于1,这样才能保证进入排序循环。


这里构造的rrsetidx为0x3。


14.png


正常数据包如下图所示:


15.png


Answers块中,p1指向第一个资源记录,p2指向第二个,然后进行排序。


16.png


分别跳过Class,Type和TTL,到达RDATA区域。


17.png


Data lengh为20,为Name Server的长度。然后进入排序循环。


18.png


行315,首先调用get_rdata()函数解析第一个资源记录p1的RDATA域中的NameServer,看下该函数实现。


19.png


判断d是否等于-1,这里不等于,不进入if语句,来到如下代码。


20.png


然后调用extract_name()函数解析,这里需要保证extract_name()函数解析错误返回0,保证进入get_rdata()函数返回为0,通过设置超长NameServer字符串即可。


21.png


进入if语句,行318,计算len1,为end1-p1,即是NameServer的长度。行319,调用memcpy()将p1拷贝到buff1+left1中。


22.png


这里len1设置为3550,p1为NameServer,长度RDLENGTH为用户可控。前文已经介绍buff1为daemon>workspacename,大小为2020,因此发生堆溢出。


23.png


缓解措施


● 升级dnsmasq到最新版本(2.83及以上),这是目前最有效的方法。

● 如果不必要,配置dnsmasq设备不要在WAN口监听。

●  尽量配置dnsmasq最大转发查询条目小一点。

● 暂时关闭DNSSEC验证选项。

● 使用为DNS提供传输安全的协议,如DoT或DoH。


参考链接:


[1] https://www.jsof-tech.com/disclosures/dnspooq/

[2] https://www.jsof-tech.com/wp-content/uploads/2021/01/DNSpooq-Technical-WP.pdf

[3] https://www.rfc-editor.org/rfc/rfc1664.txt

[4] https://security.googleblog.com/2017/10/behind-masq-yet-more-dns-and-dhcp.html

[5] https://spoofer.caida.org/summary.php

[6] https://www.rfc-editor.org/rfc/rfc7858.txt

[7] https://www.rfc-editor.org/rfc/rfc5452.txt

[8] http://www.thekelleys.org.uk/dnsmasq/doc.html

[9]https://dl.acm.org/doi/10.1145/3372297.3417280

[10] https://github.com/Z3Prover/z3

[11] https://www.chromium.org/developers/design-documents/dns-prefetching

[12] https://www.rfc-editor.org/rfc/rfc4033.txt

[13] https://zhuanlan.zhihu.com/p/92899876


启明星辰积极防御实验室(ADLab)


ADLab成立于1999年,是中国安全行业最早成立的攻防技术研究实验室之一,微软MAPP计划核心成员,“黑雀攻击”概念首推者。截止目前,ADLab已通过CVE累计发布安全漏洞近1100个,通过 CNVD/CNNVD累计发布安全漏洞1000余个,持续保持国际网络安全领域一流水准。实验室研究方向涵盖操作系统与应用系统安全研究、智能终端安全研究、物联网智能设备安全研究、Web安全研究、工控系统安全研究、云安全研究。研究成果应用于产品核心技术研究、国家重点科技项目攻关、专业安全服务等。


adlab.jpg