漏洞简介
在Windows上,DNS服务器是域控制器,其管理员是Domain Admins组的一部分。默认情况下,Domain Admins组是已加入域的所有计算机上Administrators组的成员,包括域控制器[8]。如果利用得当,攻击者可以在易受攻击的系统上远程执行代码,并获得Domain Admin权限,从而有效地损害了整个公司的基础架构。
DNS服务器-dns.exe负责在安装了DNS角色的Windows服务器上回答DNS查询。
CVE-2020-1350是DNS.exe在处理畸形DNS Sig消息时,由于对数据包的字段校验不严格,导致了整型溢出。DNS SIG 消息中包含对DNS记录集合的数字签名,DNS记录集合就是一种名字相同,或者类型相同的DNS消息集合。
漏洞类型:整数溢出导致基于堆的缓冲区溢出
攻击流程
攻击者可以配置一个恶意的域名evildomain.com,该域名的DNS指向的DNS服务器作为本次攻击的目标。
- 当客户端查询evildomain.com的DNS记录;
- 目标DNS服务器向根域名服务器查询evildomain.com的NS记录;
- 根域名服务器告诉目标DNS,evildomain.com的权威DNS服务器是3.3.3.3,并该记录缓存起来;
- 客户端向目标服务器查询evildomain.com的Sig记录;
- 目标服务器将请求转发给权威DNS服务器;
- 权威DNS服务器返回畸形Sig查询结果;
目标DNS服务器处理SigQuery的畸形消息时,将此消息缓存到自己的记录中,触发漏洞。
漏洞函数
简言之就是整数溢出后导致堆溢出,将该漏洞抽象为以下模型:
void Examples(char *in){
unsigned short len_p1 = len ( in.p1); unsigned short len_p2 =len ( in.p2);
unsigned short len_p = len_p1 + len_p2;
char * buf = (char *)malloc(len_p);
memcpy(buf, in.p1, len_p1); }
问题就出在 len_p 的计算上,假设 len_p1 = 0xFFD0,len_p2 = 0x40,相加后高位数据被丢弃,那么 0xFFD0 + 0x40 = 0x10010 = 0x10
,所以实际分配的内存是 0x10 而不是 0x10010,而之后往 0x10 的空间内拷贝 0xFFD0 长度的数据,自然造成了内存溢出。
而在dns.exe!SigWireRead
函数里面也有类似的溢出漏洞,如图所示第11行:
漏洞成因如下:ushort类型,为无符号16位类型。RR_AllocateEx 分配的大小只有16个bit,大小为0~65535,所以只要构造size 大于65535,造成整数溢出,而系统就会分配一个比实际数据量小的堆块,进而造成堆溢出,将整数溢出转化成堆溢出漏洞。
漏洞利用:
因此,触发此漏洞所需的全部操作就是让受攻击的DNS服务器向我们查询SIG记录,并使用长签名(长度&>=64KB)对其作出SIG响应。但是,UDP上的DNS的大小限制为512字节(如果服务器支持EDNS0,则为4096字节)。在任何情况下,这都不足以触发漏洞。
CheckPoint 找到一种方法可以突破上述限制:UDP 响应时将 DNS 头部的 TC
标志置 1,表示被截断,之后客户端便会尝试用 TCP 协议和服务器建立连接,并通过 TCP 协议传递数据。由于消息的前两个字节表示其长度,因此TCP上的DNS中消息的最大大小表示为16位,因此限制为64KB。TCP 协议在 53 端口上可传输长度最大为 65535 的 DNS 数据,这无疑给该漏洞创造了先决条件。如下图:红框中的表示TC置位字段:
但是,即使是长度为65,535的消息也不足以触发漏洞,因为消息长度包括报头和原始查询。计算传递给RR_AllocateEx的大小时不考虑此开销。那么,该漏洞如何触发呢?继续往下看,首先了解一下域名编码和域名压缩。
域名编码
eg:对于完整的一个域名www.baidu.com,需要14个字节:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
3 | w | w | w | 5 | b | a | i | d | u | 3 | c | o | m |
可以看到第一个字节 是3,表示之后会有3个字节的字符。
域名压缩
域名压缩是 DNS 协议节省空间的一种方式,因为请求中的域名字符会频繁出现在响应包的内,故采用了一种标志+偏移的格式来压缩域名。压缩方式很简单:1 + 1 + offset
,即最高 2bit 位为 1 时代表了将采用域名压缩,剩余的组合为偏移值(基地址为 DNS 头),如下图:响应区域中的 Name 字段为 0xc00c
,明显是被压缩过的,最高 2bit 位是标志,剩余的 bit 位组合得到的值为 0xc,所以当前 Name 表示的字符串在 DNS 头部向后偏移 0xc 的位置,也就是图中红框中 0x3 开始(0x50 表示 DNS 头起始位置)的域名 bbs.pediy.com
。
因此,可以将域名压缩的偏移值指向一片可控的区域,而不是指向查询时域名,就完全有可能使得域名的长度大大增加,比如在这里修改为c0 0d
,那么表示域名从0x62开始,说明后面有0x62个字节的字符,然后依次。这样就使得源代码第11行中的 sigName.Length 的大小变大。
注意:DNS name长度最大为0XFF
所以,可以在不改变原来数据包大小的情况下,将sigLength+sigName.length+0x16 的大小,大于65535,造成整数溢出。分配一个小的空间缓存,然后导致堆溢出。
POC分析和漏洞复现
部分poc代码,大体就是构造sig响应消息。具体POC,在这里:https://github.com/maxpl0it/CVE-2020-1350-DoS
实验环境:
Windows Server 2012 R2 192.168.29.138
KaLi Linux 192.168.29.135
复现过程:
1.在windows server 2008 安装dns服务器,并配置静态ip
2.在kali 中运行脚本,开启ibrokethe.net 域名的恶意服务器,这里的ibrokethe.net 表示恶意域名,也可以代替为其他域名。
sudo python sigred_dos.py ibrokethe.net
3.在受害者服务器配置转发器,转发器ip为kali 机的ip。
4.在受害者服务器使用nslookup命令查询9.ibrokethe.net:
nslookup -type=sig 9.ibrokethe.net 127.0.0.1
5.查看kali,发现会发现收到了一个UDP连接,又收到了一个TCP连接。
最后要说的是,复现过程采用了Windows DNS服务器的条件转发器,略过了向权威NS查询的步骤,直接向恶意DNS服务器发出查询。
复现结果:
抓包结果如下:
由于本地无法解析“ibrokethe.net”,因此会以UDP协议向恶意服务器kali发送DNS查询,也就是kali收到的第一个UDP连接。恶意服务器查到了该域名,就向受害者发送响应包,设置TC位,通知受害者采用TCP重发原来的查询请求,并允许返回的响应报文超过512个字节。这是第二个DNS包。然后进行三次握手,受害者再次以TCP协议发送请求,这就是kali收到的TCP连接。之后,恶意服务器就可以利用TCP传输大于64KB的响应包发给受害者,触发漏洞,造成堆溢出。
然后,就可以进行远程代码执行等操作了。
可以看到在复现的过程,产生堆溢出,dns.exe进程发生崩溃,从而产生DOS攻击。如下图:
漏洞修复
临时修复方案
修改注册表,限制tcp包的长度,TcpReceivePacketSize的值为0xFF00
HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesDNSParameters
DWORD = TcpReceivePacketSize
Value = 0xFF00
注意:必须重新启动 DNS 服务才能生效。
补丁更新
可看到右图24行,在前面对整数溢出做了个判断,即sigLength+sigName.Length的值要大于等于sigLength
总结:
协议方面多多少少都会有我们所疏忽的问题,对于dns.exe开发者,最开始并未考虑到最大可以超过65535,造成溢出,代码并没有问题,造成该漏洞的关键点,主要是因为域名压缩。往往很多个疏忽点,凑到了一起就成了攻击者利用的漏洞。
参考链接:
CVE-2020-1350 Windows Server DNS漏洞复现
漏洞利用剖析:带有CVE-2020-1350 SIGRed的RCE
CVE-2020-1350: Windows DNS Server蠕虫级远程代码执行漏洞分析
Windows DNS server从cve-2020-1350到内存泄露