本文为看雪论坛精华文章
看雪论坛作者ID:Saturn35
CVE-2020-1350是微软在2020年7月修复的DNS协议相关漏洞,CVSS给出了高达10分的评分。漏洞发现的细节最初由CheckPoint进行纰漏,随后FSecureLabs的@maxpl0it研究员给出了相关POC脚本,我会在文末给出相关链接。本文主要在肝完checkpoint的文章的基础上,通过[4][5]两篇文章的启发,使用POC进行漏洞分析和复现。测试环境:Win Server2019+Win Server2016本文原文地址:https://saturn35.com/2020/07/24/20200724-1/#more
根据checkpoint的描述,以及对相关补丁的diff后,找到漏洞函数为:可以看到在申请内存函数RR_AllocateEX的参数中,第一参数v9+v13+20表示分配大小,其值由[Name_PacketNameToCountNameEx result]+ [0x14]+ [signature’s length (rdi–rax)] 决定。当此值结果在CX和DI寄存器中传递,而16位寄存器最大值为65535,当上数值结果大于65535时,将发生溢出,而真正申请到的数据是比要申请的大小小得多。而后续我们可以看到存在memcpy拷贝函数,那么,可能存在由整数溢出的导致基于堆的缓冲区溢出。
Header表示DNS数据头,Question表示查询区,Answer表示应答区,后两个为认证区和附加区。Header部分始终存在。大小为12字节,包含以下字段:TC:指定此消息由于长度大于传输通道上允许的长度而被截断。在DNS协议中,默认采用的传输层协议为用户数据报协议(UDP),但UDP传输的DNS数据包限制最大为512B,并不足以触发漏洞。而此标志位是漏洞触发的关键,当其被设置为1是,在应答时,表示通知客户端,数据量比较大,让客户端用TCP重发一次查询请求,而基于TCP的DNS数据包大小可大于512字节。比较有趣的是域名的构成是分段式的len+str的形式:eg:对于完整的一个域名,www.baidu.com,需要14个字节。规定域名段的长度不能超过63个字节,所以表示域名段长度的字节最高两位没有用到,因此另有用途。(2)当最高两位为11时,表示将域名进行压缩,该字节去掉最高两位后剩下的6位,以及后面的8位总共14位,指向DNS数据报文中的某一段域名,相当于一个指针。如0xc00c, 表示从DNS正文(UDP payload)的偏移offset=0x0c处所表示的域名。(3)混合表示:以www.baidu.com为例,假设该域名位于DNS报文偏移0x20处,可能的用法有:
a、0xc020:表示完整域名www.baidu.com
b、0xc024:从完整域名的第二段开始,指代baidu.com
c、0x016dc024:0x01表示后面跟的size大小1,0x6d表示字符m,所以0x016d表示字符串"m",第二段0xc024指代baidu.com,因此整段表示m.baidu.com
在漏洞函数中,当读取SIG类型响应包才回进入触发流程,因此有必要了解下其结构。在RFC2535官方文档中,对SIG资源记录的概括如下:The SIG or "signature" resource record (RR) is the fundamental way that data is authenticated in the secure Domain Name
System (DNS). As such it is the heart of the security provided.The SIG RR unforgably authenticates an RRset [RFC 2181]
of a particular type, class, and name and binds it to a time interval and the signer's domain name. This is done
using cryptographic techniques and the signer's private key. The signer is frequently the owner of the zone from which
the RR originated. The type number for the SIG RR type is 24.其中,signer's name表示生成SIG RR的签名者的域名。这是可用于验证签名的公钥RR的所有者名。通常是包含被认证的RRset的区域......可以使用标准的DNS名称压缩来压缩签名者的名称。
在maxpl0it的POC中,思路是受害DNS服务器向evil服务器发送sig类型的域名查询时,通过启动UDP Server和TCP Server 监听当前设备IP 0.0.0.0和53端口,在程序中当收到指定类型和域名的DNS查询时,伪造DNS应答数据,设置TC位为1。通知受害者服务器重新用TCP发送查询。但是,TCP传输DNS包大小限制为65535,还不足以触发。那么,根据checkpoint的介绍,用到另一个技术,DNS域名压缩。根据A warm welcome to DNS, powerdns.org:官方文档描述“为了将尽可能多的信息压缩到512字节中,可以(通常必须)压缩DNS名称……在这种情况下,应答的DNS名称编码为0xc0 0x0c。c0部分设置了两个最高有效位,表示接下来的6 + 8位是指向消息中较早位置的指针。在这种情况下,它指向数据包中紧靠DNS标头的位置12(= 0x0c)。”函数Name_PacketNameToCountNameEx根据Signer's Name 来获取域名的大小。0xc00c(偏移从Transaction ID 0x8380开始计算)指向域名9.ibrokethe.net,大小则为0x11。(1+1+1+9+1+3+1=17)但通过设置Signer's Name 为0xc00d,将域名起始位置指向"9",此时会将"9"当作<size>,将后面的0x39个字节当作域名段,并且一直解析下去,直至解析到'\0'。如下图,Name_PacketNameToCountNameEx会将图中选中的部分当成域名,获取大小。此时大小为(0x39+1)+(0xf+1)*5 +1 =0x8b。(0x39和0xf表示域名段大小,+1表示".", 最后一个+1表示"\0")。Windbg中结果如下,可以看到计算结果为0x8b:传入dns!Name_PacketNameToCountNameEx第一参数[rsp+30h]拿到了获取到的域名大小,rdi和rax分别表示signature的结束地址和起始地址,最后算出此段数据大小为0xffaa。那么,最终上述公式的最终结果为0x8b+0x14+0xffaa & 0xffff = 0x49:将此值作为第一参数,传入RR_AllocateEX函数,最终申请堆内存大小为0x49,而后续进行memcpy拷贝时,拷贝signature字段的数据,拷贝到刚刚申请的内存,大小为0xffaa,最终产生溢出,崩溃。可以看到,我们伪造的DNS应答数据如下,当设置TC位为1时,则向受害服务器表面需要重新发送一次TCP查询,后续可以看到开始TCP握手,并进行应答。最后要说的是,复现过程采用了Windows DNS服务器的条件转发器,略过了向权威NS查询的步骤,直接向恶意DNS服务器发出查询。1. VMware双虚拟机最好采用NAT,同时关闭防火墙。2.关闭恶意DNS服务器所在主机的DNS服务,否则影响脚本中的监听服务器。3. 方便理解,可将POC中拆分为两个脚本,避免多线程问题。
可以看到,在7月份的更新中,微软对传入内存申请的大小做了判断,V11+v9>V11,即防止了溢出,使得后续申请到正常大小的内存,可以看到修补的比较直接。
[1] SIGRed – Resolving Your Way into Domain Admin: Exploiting a 17 Year-old Bug in Windows DNS Servershttps://research.checkpoint.com/2020/resolving-your-way-into-domain-admin-exploiting-a-17-year-old-bug-in-windows-dns-servers/[2] CVE-2020-1350 (SIGRed) - Windows DNS DoS Exploithttps://github.com/maxpl0it/CVE-2020-1350-DoS[3] CVE-2020-1350: Windows DNS Server蠕虫级远程代码执行漏洞分析https://cert.360.cn/report/detail?id=5b7082dae4756f361d43a5efde233eddhttps://bbs.pediy.com/thread-260712.htmhttps://tools.ietf.org/html/rfc1035
看雪ID:Saturn35
https://bbs.pediy.com/user-831334.htm
*本文由看雪论坛 Saturn35 原创,转载请注明来自看雪社区。
ps. 觉得对你有帮助的话,别忘点分享,点赞和在看,支持看雪哦~