TCP/IP协议栈系列误差(NAME:WRECK)剖析

宣布时间 2021-04-25

误差概述


2021年04月13日,Fourscore研究实验室与JSOF合作,披露了一组新的DNS误差,被称为NAME:WRECK 。这些误差影响了四种盛行的TCP/IP客栈--即FreeBSD、IPnet、Nucleus NET和NetX,它们普遍保存于着名的IT软件和盛行的IOT/OT固件中,并有可能影响全球数百万的物联网装备 。攻击者可以使用这些误差使受影响的装备脱机或对装备举行控制 。


相关先容


1、DNS协议之压缩指针


在之前的文章中,我们先容了基础的DNS协议,其中域名是由一连串的label组成的,如下图所示:


1.png


其中红框所示为每个label的长度,每个label最长为63字节,并且处置惩罚的时间,除了第一个长度字节,将每个长度字节替换为”.”,最后遇到null字节竣事,从而组成了最后的域名 。


不过在一些回复包中,会包括多次A纪录或CNAME纪录,这就造成了DNS数据过于冗长,因此DNS协议的设计者设计出了压缩指针 。在压缩指针的机制中,通过指针指向之前泛起过的一连串label从而抵达压缩的目的 。这个指针由两个字节组成,第一个字节的前两个bits为11,后面14个bits为偏移地址 。

 

2.png


下面举一个详细的例子,如下图数据包所示,红框内为压缩指针,指向偏移0x0c的位置,也就是www.example.com的开头处 。


3.png


2、DHCP协议


 DHCP动态主机设置协议,前身是BOOTP协议,是一个局域网的网络协议,使用UDP协议事情,通常被用于局域网情形,主要作用是集中地管理、分派IP地址,使客户端动态的获得IP地址、Gateway地址、DNS服务器地址等信息,并能够提升地址的使用率 。


DHCP报文共有8种,划分如下所示:

DHCPDISCOVER :客户端最先DHCP历程发送的报文,是DHCP协议的最先 。

DHCPOFFER:服务器吸收到DHCPDISCOVER之后做出的响应,包括了给予客户端的IP(yiaddr)、客户端的MAC地址、租约逾期时间、服务器的识别符以及其他信息 。

DHCPREQUEST:客户端关于服务器发出的DHCPOFFER所做出的响应 。在续约租期的时间同样会使用 。

DHCPACK:服务器在吸收到客户端发来的DHCPREQUEST之后发出的乐成确认的报文 。在建设毗连的时间,客户端在吸收到这个报文之后才会确认分派给它的IP和其他信息可以被允许使用 。

DHCPNAK:DHCPACK的相反的报文,体现服务器拒绝了客户端的请求 。

DHCPRELEASE:一样平常泛起在客户端关机、下线等状态 。这个报文将会使DHCP服务器释放发出此报文的客户端的IP地址 。

DHCPINFORM:客户端发出向服务器请求一些信息的报文 。

DHCPDECLINE:当客户端发明服务器分派的IP地址无法使用(如IP地址冲突时),将发出此报文,通知服务器榨取使用该IP地址 。


DHCP数据包发送历程,如下图所示:


4.png


DHCP报文名堂如下图所示:


5.png


部分数据域界说,如下所示:


xid:随机天生的一段字符串,两个数据包拥有相同的xid说明他们属于统一次会话 。

ciaddr:客户端会在发送请求时将自己的IP放在此处 。

yiaddr:服务器会将想要分派给客户端的IP放在此处 。

siaddr:指导程序中使用的下一个服务器的IP地址;由服务器在DHCPOFFER,DHCPACK中返回 。

chaddr:客户端的MAC地址 。

giaddr:若是需要跨子网举行DHCP地址发放,则在此处填入经由的路由器的IP地址 。

sname:服务器主域名 。

options:可以自由添加的部分,用于存放客户端向服务器请求信息和服务器的应答信息 。


DHCP域搜索选项,该选项从DHCP服务器转达到DHCP客户端,以指定在使用DNS剖析主机名时使用的域搜索列表 。该选项的代码为119,名堂如下图所示:


6.png


 举个例子,下图是“eng.apple.com”和“marketing.apple.com”组成的搜索列表的示例编码:


7.png


该示例编码已分为三个“域搜索选项” 。在客户端剖析之前,所有域搜索选项在逻辑上都串联到一个数据块中 。以第一个“域搜索选项”为例,第一个字节为119,第二个字节为9,体现后面Searchstring的长度,剩下的数据均为Searchstring 。这三个“域搜索选项”的Searchstring组合成一个完整聚合块,可体现为:

|3|’e’|’n’|’g’|5|’a|’p’|’p|’l’|’e’|3|’c’|’o’|’m’|0|9|’m’|’a’|’r’|’k’|’e’|’t’|’i’|’n’|’g’|0xC0|0x04| 。


“eng.apple.com”的编码以零最后,以标记名称的最后 。“marketing”(针对marketing.apple.com)的编码以两个八位字节的压缩指针C004(十六进制)最后,该指针指向DomainSearchOption数据的完整聚合块(从第一个“域搜索选项”中的Searchstring最先)中的偏移量4,其中另一个有用编码可以找到完整的域名(“apple.com”) 。如下图所示:

 

8.png


每个搜索域名都必需以零或两个八位位组压缩指针最后 。若是吸收器抵达搜索列表选项数据的完整汇总块的末尾时正在通过搜索域名举行解码,而没有找到零或有用的两个八位位组压缩指针,那么必需将部分读取的域名视为无效域名 。


误差剖析


1、Nucleus NET系列误差


9.png


上图为Nucleus NET协议栈中的DNS_Unpack_Domain_Name()函数,这个函数用来处置惩罚DNS应答纪录 。第一个参数dst是指向一个buffer,用于拷贝剖析的域名 。第二个参数src指向域名的第一个字节,第三个参数指向DNS Header的第一个字节 。


代码通过while循环去剖析域名(第7行),直到src为null字节,也就是域名的最后 。之后将第一个label的长度赋值给size(第9行) 。下面,也就是最主要的一步就是检查该字节是否为压缩指针 。若是不是,src指针前移一个字节,然后将src拷贝到dst 。然后每个label之间加”.” 。为正常的剖析域名士程 。若是是压缩指针并且是第一个压缩指针,retval加两个字节(第10,11行),然后凭证偏移盘算label起始位置,然后将长度赋值给size,之后正常处置惩罚 。


这段代码看起来没有问题现实上包括4个很是严重的问题,其造成的误差编号划分为CVE-2020-27736、CVE-2020-27738、 CVE-2020-15795、CVE-2020-27009 。


(1)对label长度没有做验证

凭证上文讲述的内容,每个label的第一个字节代表长度(第8,16行),可是程序没有检查这个长度是否代表真实数据包中label的长度,这可导致读取凌驾已分派结构体的buffer,造成拒绝服务 。


(2)对压缩指针的偏移没有做验证

凭证上文讲述的内容,程序判断为压缩指针后,便会通过给出的偏移去寻找剖析label(第14,15,16行),可是程序没有验证偏移的规模,这导致偏移值可以恣意给定,这导致可以越界读写,从而RCE 。


(3)缺少NULL终止判断

凭证RFC1035的表述,NULL字节(0x00)体现name的最后(第7行) 。可是在许多DNS剖析程序中缺乏对NULL字节的验证,这导致攻击者可以通过控制NULL字节在特定的位置,通过和前几个问题相团结,同样可以实现可控的内存读写 。


(4)对域名的长度没有做限制

凭证RFC1035的陈述,从DNS纪录里提取的域名不应该凌驾255字节,只管每个label限制了不凌驾63个字节(第15行),可是这只是一次性拷贝的长度,并没有限制现实拷贝的长度 。如下图所示,通过NU_Allocate_Memory()函数分派给name 255个字节个空间(第50行),即DNS_MAX_NAME_SIZE 。后面挪用DNS_Unpack_Domain_Name的历程中,显然可以通过结构恶意的数据包溢出这255字节 。

 

10.png


以是凭证以上内容,PoC的编写的思绪有许多要领,好比可以让程序永远无法退出循环,类似下图:

 

11.png


c0为压缩指针,1e为偏移量,而偏移的位置正好重新指向c0,造成无限循环 。


2、FreeBSD误差(CVE-2020-7461)


该误差泛起在dhclient剖析DHCP数据包中的“域搜索选项”数据时,由于界线检查过失而导致堆溢出 。dhclinet是FreeBSD系统中用于提供DHCP服务的二进制程序,执行下令:dhclient em0便可举行DHCP设置,em0为网卡 。源码位于sbin\dhclient\options.c,从do_packet()函数最先,代码清单如下图所示:


12.png


该函数用于处置惩罚DHCP客户端吸收到的数据包,行890,挪用parse_options()函数剖析数据包中的options,代码清单如下图所示:


13.png


行106,挪用expand_domain_search()函数进一步剖析DHCP域搜索选项数据,该函数举行两个操作,第一步操作是先获取所有域名标签的总长度,第二步操作是凭证第一步获取的长度举行内存分派,并拷贝所有域名标签 。

先看第一操作,怎样获取所有域名标签的长度,要害代码实现如下图所示:


14.png


首先判断options是否为空,不为空就获取options,行229,然后进入while循环,挪用find_search_domain_name_len()函数处置惩罚options,该函数通过一个while循环逐个字节剖析Searchsting并分类处置惩罚,第一种情形的要害代码实现如下图所示:

 

15.png


若是读取到data[i]为0,体现域名标签的最后,并返回该域名标签长度 。接着第二种情形的要害代码实现如下图所示:


16.png


若是读取到data[i]为0xC0,体现为压缩指针,指向另一个域名标签,行287,盘算pointer,然后对该指针举行规模检查判断是否越界(第299行),递归挪用find_search_domain_name_len()函数剖析压缩指针指向的另一个域名标签(第301行),递归挪用返回后,举行domain_name_len += pointer_len累加 。若是既不是0最后也不是压缩指针,则依次累加并移位游标,实现代码如下图所示:


17.png


在第299行和第301行之间是保存问题的,若是递归处置惩罚压缩指针指向的另一个域名标签不正那时,返回的pointer_len为-1,这里并没有将其视为无效并举行返回,而是依旧返回部分域名标签长度 。

再看第二操作,分派缓冲区并举行域名标签拷贝,要害代码如下图所示:


18.png


这里expanded_len为盘算出来的域名标签的长度(第242行),分派一段内存(第248行),进入while循环,挪用expand_search_domain_name()函数举行域名标签拷贝,实现代码如下图所示:


19.png


该函数和find_search_domain_name_len()函数实现基本是一样的,分类处置惩罚0最后和压缩指针,可是在处置惩罚压缩指针的情形时,并没有判断指针规模的合理性 。行366,挪用memcpy举行域名标签拷贝,拷贝长度为label_len 。


凭证前文剖析,第一步操作在递归处置惩罚压缩指针的情形时,是保存问题的,并没有扬弃包括无效压缩指针的域名标签 �?梢越峁挂桓鎏厥獾挠蛎昵┗煜齦ebal的递归剖析,该域名标签体现为:| 1 |x3F|x00| 1 |'A'|xC0|x01| 。


依次读取到label_len为0xC0时,进一步递归挪用剖析,这次读取到label_len为0x3f,将0x3f当成了域名长度,可是第305行,判断爆发越界,因此返回-1 。


20.png


可是这里仍然盘算出domain_name_lan为1,因此第二个域名包括一个0x41 。


21.png


最后盘算出expanded_len为0x5,如下图所示:


22.png


然后最先举行第二步拷贝操作,先拷贝第一个domain:0x3f 。如下图所示:

 

24.png


第二次拷贝第二个domain:0x41 。如下图所示:

 

25.png


递归剖析压缩指针0XC004时,爆发了混淆,如下图所示:


26.png


过失地将0x3f当成label_len,这显着是大于expanded_len的,直接拷贝导致溢出 。不过在现实测试中,并未爆发内存破损,而可以结构其他的域名标签陷入无限递归,让dhclient历程客栈耗尽导致瓦解,造成拒绝服务 。


27.png

 

处置惩罚建议


FreeBSD、Nucleus NET和 NetX,建议先实验以下清静建议,再实时更新装备供应商宣布的清静更新 。

清静建议:


使用一些缓解信息来开发检测DNS误差的署名;

发明并清点运行易受攻击客栈的装备;

实验分段控制和适当的network hygiene;

监视受影响的装备供应商宣布的补� �;

设置装备依赖内部DNS服务器;

监控所有网络流量中的恶意数据包 。